diff mbox series

c++: return-type-req in constraint using only outer tparms [PR104527]

Message ID 20220214163250.3454164-1-ppalka@redhat.com
State New
Headers show
Series c++: return-type-req in constraint using only outer tparms [PR104527] | expand

Commit Message

Patrick Palka Feb. 14, 2022, 4:32 p.m. UTC
Here the template context for the atomic constraint has two levels of
template arguments, but since it depends only on the innermost argument
T we use a single-level argument vector during substitution into the
constraint (built by get_mapped_args).  We eventually pass this vector
to do_auto_deduction as part of checking the return-type-requirement
inside the atom, but do_auto_deduction expects outer_targs to be a full
set of arguments for sake of satisfaction.

do_auto_deduction has a workaround in place to compensate for callers
that pass only the innermost arguments as outer_targs, but here we're
passing the _outermost_ arguments.  Since the former situation should
now (after r12-7101) only occur with adc_unify callers and the latter
only with adc_requirement callers, this patch conditions the existing
workaround according to the auto_deduction_context: if the context is
adc_requirement, we add dummy innermost levels, otherwise we add dummy
outermost levels as before and also assert that the context is adc_unify.

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

	PR c++/104527

gcc/cp/ChangeLog:

	* pt.cc (do_auto_deduction): When template argument levels are
	missing from outer_targs, fill in the innermost rather than the
	outermost levels with dummy args if the context is
	adc_requirement, otherwise also assert that the context is
	adc_unify.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp2a/concepts-return-req4.C: New test.
---
 gcc/cp/pt.cc                                  | 28 +++++++++++++------
 .../g++.dg/cpp2a/concepts-return-req4.C       | 24 ++++++++++++++++
 2 files changed, 44 insertions(+), 8 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-return-req4.C

Comments

Patrick Palka Feb. 14, 2022, 4:37 p.m. UTC | #1
On Mon, 14 Feb 2022, Patrick Palka wrote:

> Here the template context for the atomic constraint has two levels of
> template arguments, but since it depends only on the innermost argument
> T we use a single-level argument vector during substitution into the
> constraint (built by get_mapped_args).  We eventually pass this vector
> to do_auto_deduction as part of checking the return-type-requirement
> inside the atom, but do_auto_deduction expects outer_targs to be a full
> set of arguments for sake of satisfaction.
> 
> do_auto_deduction has a workaround in place to compensate for callers
> that pass only the innermost arguments as outer_targs, but here we're
> passing the _outermost_ arguments.  Since the former situation should
> now (after r12-7101) only occur with adc_unify callers and the latter

Whoops, this should be r12-6919 (after which we pass outer_targs
appropriately during adc_variable_type deduction of non-function-scope
variables), not r12-7101, sorry about that.

> only with adc_requirement callers, this patch conditions the existing
> workaround according to the auto_deduction_context: if the context is
> adc_requirement, we add dummy innermost levels, otherwise we add dummy
> outermost levels as before and also assert that the context is adc_unify.
> 
> Bootstrapped and regtested on x86_64-pc-linux-gnu and tested on cmcstl2
> and range-v3, does this look OK for trunk?
> 
> 	PR c++/104527
> 
> gcc/cp/ChangeLog:
> 
> 	* pt.cc (do_auto_deduction): When template argument levels are
> 	missing from outer_targs, fill in the innermost rather than the
> 	outermost levels with dummy args if the context is
> 	adc_requirement, otherwise also assert that the context is
> 	adc_unify.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/cpp2a/concepts-return-req4.C: New test.
> ---
>  gcc/cp/pt.cc                                  | 28 +++++++++++++------
>  .../g++.dg/cpp2a/concepts-return-req4.C       | 24 ++++++++++++++++
>  2 files changed, 44 insertions(+), 8 deletions(-)
>  create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-return-req4.C
> 
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index 1b18e2a7787..4ff2710b8ba 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -30215,20 +30215,32 @@ do_auto_deduction (tree type, tree init, tree auto_node,
>  
>        tree full_targs = add_to_template_args (outer_targs, targs);
>  
> -      /* HACK: Compensate for callers not always communicating all levels of
> -	 outer template arguments by filling in the outermost missing levels
> -	 with dummy levels before checking satisfaction.  We'll still crash
> -	 if the constraint depends on a template argument belonging to one of
> -	 these missing levels, but this hack otherwise allows us to handle a
> -	 large subset of possible constraints (including all non-dependent
> -	 constraints).  */
>        if (int missing_levels = (TEMPLATE_TYPE_ORIG_LEVEL (auto_node)
>  				- TMPL_ARGS_DEPTH (full_targs)))
>  	{
>  	  tree dummy_levels = make_tree_vec (missing_levels);
>  	  for (int i = 0; i < missing_levels; ++i)
>  	    TREE_VEC_ELT (dummy_levels, i) = make_tree_vec (0);
> -	  full_targs = add_to_template_args (dummy_levels, full_targs);
> +	  if (context == adc_requirement)
> +	    /* We're checking a requires-expr's return-type-requirement that's
> +	       part of an atomic constraint that doesn't depend on any innermost
> +	       template arguments, so OUTER_TARGS (built by get_mapped_args) is
> +	       missing at least one innermost level.  Fill in the innermost
> +	       levels of OUTER_TARGS with dummy levels.  */
> +	    full_targs = add_to_template_args
> +	      (add_to_template_args (outer_targs, dummy_levels), targs);
> +	  else
> +	    {
> +	      /* Otherwise, fill in the _outermost_ levels with dummy levels.
> +		 This compensates for adc_unify callers that only pass the
> +		 innermost level of template arguments as OUTER_TARGS.  We'll
> +		 still crash if the constraint depends on a template argument
> +		 belonging to one of these missing levels, but this hack
> +		 otherwise allows us to handle a large subset of possible
> +		 constraints (including all non-dependent constraints).  */
> +	      gcc_checking_assert (context == adc_unify);
> +	      full_targs = add_to_template_args (dummy_levels, full_targs);
> +	    }
>  	}
>  
>        if (!constraints_satisfied_p (auto_node, full_targs))
> diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-return-req4.C b/gcc/testsuite/g++.dg/cpp2a/concepts-return-req4.C
> new file mode 100644
> index 00000000000..471946bc8eb
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-return-req4.C
> @@ -0,0 +1,24 @@
> +// PR c++/104527
> +// { dg-do compile { target c++20 } }
> +
> +template<class T, class U>
> +concept is_same = __is_same(T, U);
> +
> +template<class T>
> +struct A {
> +  template<class...>
> +    requires requires { { 0 } -> is_same<T>; }
> +  struct B {};
> +
> +  template<class...>
> +    requires requires { { 1 } -> is_same<T>; }
> +  static void f();
> +};
> +
> +A<int>::B<> a1;
> +A<bool>::B<> a2; // { dg-error "constraint" }
> +
> +int main() {
> +  A<int>::f();
> +  A<bool>::f(); // { dg-error "no match" }
> +}
> -- 
> 2.35.1.102.g2b9c120970
> 
>
Jason Merrill Feb. 15, 2022, 11:39 p.m. UTC | #2
On 2/14/22 11:32, Patrick Palka wrote:
> Here the template context for the atomic constraint has two levels of
> template arguments, but since it depends only on the innermost argument
> T we use a single-level argument vector during substitution into the
> constraint (built by get_mapped_args).  We eventually pass this vector
> to do_auto_deduction as part of checking the return-type-requirement
> inside the atom, but do_auto_deduction expects outer_targs to be a full
> set of arguments for sake of satisfaction.

Could we note the current number of levels in the map and use that in 
get_mapped_args instead of the highest level parameter we happened to use?

> do_auto_deduction has a workaround in place to compensate for callers
> that pass only the innermost arguments as outer_targs, but here we're
> passing the _outermost_ arguments.  Since the former situation should
> now (after r12-7101) only occur with adc_unify callers and the latter
> only with adc_requirement callers, this patch conditions the existing
> workaround according to the auto_deduction_context: if the context is
> adc_requirement, we add dummy innermost levels, otherwise we add dummy
> outermost levels as before and also assert that the context is adc_unify.
> 
> Bootstrapped and regtested on x86_64-pc-linux-gnu and tested on cmcstl2
> and range-v3, does this look OK for trunk?
> 
> 	PR c++/104527
> 
> gcc/cp/ChangeLog:
> 
> 	* pt.cc (do_auto_deduction): When template argument levels are
> 	missing from outer_targs, fill in the innermost rather than the
> 	outermost levels with dummy args if the context is
> 	adc_requirement, otherwise also assert that the context is
> 	adc_unify.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/cpp2a/concepts-return-req4.C: New test.
> ---
>   gcc/cp/pt.cc                                  | 28 +++++++++++++------
>   .../g++.dg/cpp2a/concepts-return-req4.C       | 24 ++++++++++++++++
>   2 files changed, 44 insertions(+), 8 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-return-req4.C
> 
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index 1b18e2a7787..4ff2710b8ba 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -30215,20 +30215,32 @@ do_auto_deduction (tree type, tree init, tree auto_node,
>   
>         tree full_targs = add_to_template_args (outer_targs, targs);
>   
> -      /* HACK: Compensate for callers not always communicating all levels of
> -	 outer template arguments by filling in the outermost missing levels
> -	 with dummy levels before checking satisfaction.  We'll still crash
> -	 if the constraint depends on a template argument belonging to one of
> -	 these missing levels, but this hack otherwise allows us to handle a
> -	 large subset of possible constraints (including all non-dependent
> -	 constraints).  */
>         if (int missing_levels = (TEMPLATE_TYPE_ORIG_LEVEL (auto_node)
>   				- TMPL_ARGS_DEPTH (full_targs)))
>   	{
>   	  tree dummy_levels = make_tree_vec (missing_levels);
>   	  for (int i = 0; i < missing_levels; ++i)
>   	    TREE_VEC_ELT (dummy_levels, i) = make_tree_vec (0);
> -	  full_targs = add_to_template_args (dummy_levels, full_targs);
> +	  if (context == adc_requirement)
> +	    /* We're checking a requires-expr's return-type-requirement that's
> +	       part of an atomic constraint that doesn't depend on any innermost
> +	       template arguments, so OUTER_TARGS (built by get_mapped_args) is
> +	       missing at least one innermost level.  Fill in the innermost
> +	       levels of OUTER_TARGS with dummy levels.  */
> +	    full_targs = add_to_template_args
> +	      (add_to_template_args (outer_targs, dummy_levels), targs);
> +	  else
> +	    {
> +	      /* Otherwise, fill in the _outermost_ levels with dummy levels.
> +		 This compensates for adc_unify callers that only pass the
> +		 innermost level of template arguments as OUTER_TARGS.  We'll
> +		 still crash if the constraint depends on a template argument
> +		 belonging to one of these missing levels, but this hack
> +		 otherwise allows us to handle a large subset of possible
> +		 constraints (including all non-dependent constraints).  */
> +	      gcc_checking_assert (context == adc_unify);
> +	      full_targs = add_to_template_args (dummy_levels, full_targs);
> +	    }
>   	}
>   
>         if (!constraints_satisfied_p (auto_node, full_targs))
> diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-return-req4.C b/gcc/testsuite/g++.dg/cpp2a/concepts-return-req4.C
> new file mode 100644
> index 00000000000..471946bc8eb
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-return-req4.C
> @@ -0,0 +1,24 @@
> +// PR c++/104527
> +// { dg-do compile { target c++20 } }
> +
> +template<class T, class U>
> +concept is_same = __is_same(T, U);
> +
> +template<class T>
> +struct A {
> +  template<class...>
> +    requires requires { { 0 } -> is_same<T>; }
> +  struct B {};
> +
> +  template<class...>
> +    requires requires { { 1 } -> is_same<T>; }
> +  static void f();
> +};
> +
> +A<int>::B<> a1;
> +A<bool>::B<> a2; // { dg-error "constraint" }
> +
> +int main() {
> +  A<int>::f();
> +  A<bool>::f(); // { dg-error "no match" }
> +}
Patrick Palka Feb. 16, 2022, 7:56 p.m. UTC | #3
On Tue, 15 Feb 2022, Jason Merrill wrote:

> On 2/14/22 11:32, Patrick Palka wrote:
> > Here the template context for the atomic constraint has two levels of
> > template arguments, but since it depends only on the innermost argument
> > T we use a single-level argument vector during substitution into the
> > constraint (built by get_mapped_args).  We eventually pass this vector
> > to do_auto_deduction as part of checking the return-type-requirement
> > inside the atom, but do_auto_deduction expects outer_targs to be a full
> > set of arguments for sake of satisfaction.
> 
> Could we note the current number of levels in the map and use that in
> get_mapped_args instead of the highest level parameter we happened to use?

Ah yeah, that seems to work nicely.  IIUC it should suffice to remember
whether the atomic constraint expression came from a concept definition.
If it did, then the depth of the argument vector returned by
get_mapped_args must be one, otherwise (as in the testcase) it must be
the same as the template depth of the constrained entity, which is the
depth of ARGS.

How does the following look?  Bootstrapped and regtested on
x86_64-pc-linux-gnu and also on cmcstl2 and range-v3.

-- >8 --

Subject: [PATCH] c++: return-type-req in constraint using only outer tparms
 [PR104527]

Here the template context for the atomic constraint has two levels of
template parameters, but since it depends only on the innermost parameter
T we use a single-level argument vector (built by get_mapped_args) during
substitution into the atom.  We eventually pass this vector to
do_auto_deduction as part of checking the return-type-requirement within
the atom, but do_auto_deduction expects outer_targs to be a full set of
arguments for sake of satisfaction.

This patch fixes this by making get_mapped_args always return an
argument vector whose depth corresponds to the template depth of the
context in which the atomic constraint expression was written, instead
of the highest parameter level that the expression happens to use.

	PR c++/104527

gcc/cp/ChangeLog:

	* constraint.cc (normalize_atom): Set
	ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P appropriately.
	(get_mapped_args):  Make static, adjust parameters.  Always
	return a vector whose depth corresponds to the template depth of
	the context of the atomic constraint expression.  Micro-optimize
	by passing false as exact to safe_grow_cleared and by collapsing
	a multi-level depth-one argument vector.
	(satisfy_atom): Adjust call to get_mapped_args and
	diagnose_atomic_constraint.
	(diagnose_atomic_constraint): Replace map parameter with an args
	parameter.
	* cp-tree.h (ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P): Define.
	(get_mapped_args): Remove declaration.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp2a/concepts-return-req4.C: New test.
---
 gcc/cp/constraint.cc                          | 64 +++++++++++--------
 gcc/cp/cp-tree.h                              |  7 +-
 .../g++.dg/cpp2a/concepts-return-req4.C       | 24 +++++++
 3 files changed, 69 insertions(+), 26 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-return-req4.C

diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index 12db7e5cf14..306e28955c6 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -764,6 +764,8 @@ normalize_atom (tree t, tree args, norm_info info)
   tree ci = build_tree_list (t, info.context);
 
   tree atom = build1 (ATOMIC_CONSTR, ci, map);
+  if (info.in_decl && concept_definition_p (info.in_decl))
+    ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P (atom) = true;
   if (!info.generate_diagnostics ())
     {
       /* Cache the ATOMIC_CONSTRs that we return, so that sat_hasher::equal
@@ -2826,33 +2828,37 @@ satisfaction_value (tree t)
     return boolean_true_node;
 }
 
-/* Build a new template argument list with template arguments corresponding
-   to the parameters used in an atomic constraint.  */
+/* Build a new template argument vector according to the parameter
+   mapping of the atomic constraint T, using arguments from ARGS.  */
 
-tree
-get_mapped_args (tree map)
+static tree
+get_mapped_args (tree t, tree args)
 {
+  tree map = ATOMIC_CONSTR_MAP (t);
+
   /* No map, no arguments.  */
   if (!map)
     return NULL_TREE;
 
-  /* Find the mapped parameter with the highest level.  */
-  int count = 0;
-  for (tree p = map; p; p = TREE_CHAIN (p))
-    {
-      int level;
-      int index;
-      template_parm_level_and_index (TREE_VALUE (p), &level, &index);
-      if (level > count)
-        count = level;
-    }
+  /* Determine the depth of the resulting argument vector.  */
+  int depth;
+  if (ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P (t))
+    /* The expression of this atomic constraint comes from a concept definition,
+       whose template depth is always one, so the resulting argument vector
+       will also have depth one.  */
+    depth = 1;
+  else
+    /* Otherwise, the expression of this atomic constraint was written in
+       the context of the constrained entity, whose template depth is that
+       of ARGS.  */
+    depth = TMPL_ARGS_DEPTH (args);
 
   /* Place each argument at its corresponding position in the argument
      list. Note that the list will be sparse (not all arguments supplied),
      but instantiation is guaranteed to only use the parameters in the
      mapping, so null arguments would never be used.  */
-  auto_vec< vec<tree> > lists (count);
-  lists.quick_grow_cleared (count);
+  auto_vec< vec<tree> > lists (depth);
+  lists.quick_grow_cleared (depth);
   for (tree p = map; p; p = TREE_CHAIN (p))
     {
       int level;
@@ -2862,12 +2868,12 @@ get_mapped_args (tree map)
       /* Insert the argument into its corresponding position.  */
       vec<tree> &list = lists[level - 1];
       if (index >= (int)list.length ())
-	list.safe_grow_cleared (index + 1, true);
+	list.safe_grow_cleared (index + 1, /*exact=*/false);
       list[index] = TREE_PURPOSE (p);
     }
 
   /* Build the new argument list.  */
-  tree args = make_tree_vec (lists.length ());
+  args = make_tree_vec (lists.length ());
   for (unsigned i = 0; i != lists.length (); ++i)
     {
       vec<tree> &list = lists[i];
@@ -2879,6 +2885,16 @@ get_mapped_args (tree map)
     }
   SET_NON_DEFAULT_TEMPLATE_ARGS_COUNT (args, 0);
 
+  if (TMPL_ARGS_HAVE_MULTIPLE_LEVELS (args)
+      && TMPL_ARGS_DEPTH (args) == 1)
+    {
+      /* Micro-optimization: represent a depth-one argument vector
+	 using a single level.  */
+      tree level = TMPL_ARGS_LEVEL (args, 1);
+      ggc_free (args);
+      args = level;
+    }
+
   return args;
 }
 
@@ -2933,7 +2949,7 @@ satisfy_atom (tree t, tree args, sat_info info)
     }
 
   /* Rebuild the argument vector from the parameter mapping.  */
-  args = get_mapped_args (map);
+  args = get_mapped_args (t, args);
 
   /* Apply the parameter mapping (i.e., just substitute).  */
   tree expr = ATOMIC_CONSTR_EXPR (t);
@@ -2955,7 +2971,7 @@ satisfy_atom (tree t, tree args, sat_info info)
   if (!same_type_p (TREE_TYPE (result), boolean_type_node))
     {
       if (info.noisy ())
-	diagnose_atomic_constraint (t, map, result, info);
+	diagnose_atomic_constraint (t, args, result, info);
       return cache.save (inst_cache.save (error_mark_node));
     }
 
@@ -2974,7 +2990,7 @@ satisfy_atom (tree t, tree args, sat_info info)
     }
   result = satisfaction_value (result);
   if (result == boolean_false_node && info.diagnose_unsatisfaction_p ())
-    diagnose_atomic_constraint (t, map, result, info);
+    diagnose_atomic_constraint (t, args, result, info);
 
   return cache.save (inst_cache.save (result));
 }
@@ -3642,11 +3658,10 @@ diagnose_trait_expr (tree expr, tree args)
     }
 }
 
-/* Diagnose a substitution failure in the atomic constraint T when applied
-   with the instantiated parameter mapping MAP.  */
+/* Diagnose a substitution failure in the atomic constraint T using ARGS.  */
 
 static void
-diagnose_atomic_constraint (tree t, tree map, tree result, sat_info info)
+diagnose_atomic_constraint (tree t, tree args, tree result, sat_info info)
 {
   /* If the constraint is already ill-formed, we've previously diagnosed
      the reason. We should still say why the constraints aren't satisfied.  */
@@ -3667,7 +3682,6 @@ diagnose_atomic_constraint (tree t, tree map, tree result, sat_info info)
   /* Generate better diagnostics for certain kinds of expressions.  */
   tree expr = ATOMIC_CONSTR_EXPR (t);
   STRIP_ANY_LOCATION_WRAPPER (expr);
-  tree args = get_mapped_args (map);
   switch (TREE_CODE (expr))
     {
     case TRAIT_EXPR:
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index f253b32c3f2..dc2429a8406 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -466,6 +466,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];
       IMPLICIT_CONV_EXPR_NONTYPE_ARG (in IMPLICIT_CONV_EXPR)
       BASELINK_FUNCTIONS_MAYBE_INCOMPLETE_P (in BASELINK)
       BIND_EXPR_VEC_DTOR (in BIND_EXPR)
+      ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P (in ATOMIC_CONSTR)
    2: IDENTIFIER_KIND_BIT_2 (in IDENTIFIER_NODE)
       ICS_THIS_FLAG (in _CONV)
       DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (in VAR_DECL)
@@ -1679,6 +1680,11 @@ check_constraint_info (tree t)
 #define ATOMIC_CONSTR_MAP_INSTANTIATED_P(NODE) \
   TREE_LANG_FLAG_0 (ATOMIC_CONSTR_CHECK (NODE))
 
+/* Whether the expression for this atomic constraint belongs to a
+   concept definition.  */
+#define ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P(NODE) \
+  TREE_LANG_FLAG_1 (ATOMIC_CONSTR_CHECK (NODE))
+
 /* The expression of an atomic constraint. */
 #define ATOMIC_CONSTR_EXPR(NODE) \
   CONSTR_EXPR (ATOMIC_CONSTR_CHECK (NODE))
@@ -8306,7 +8312,6 @@ extern tree evaluate_requires_expr		(tree);
 extern tree tsubst_constraint                   (tree, tree, tsubst_flags_t, tree);
 extern tree tsubst_constraint_info              (tree, tree, tsubst_flags_t, tree);
 extern tree tsubst_parameter_mapping		(tree, tree, tsubst_flags_t, tree);
-extern tree get_mapped_args			(tree);
 
 struct processing_constraint_expression_sentinel
 {
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-return-req4.C b/gcc/testsuite/g++.dg/cpp2a/concepts-return-req4.C
new file mode 100644
index 00000000000..471946bc8eb
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-return-req4.C
@@ -0,0 +1,24 @@
+// PR c++/104527
+// { dg-do compile { target c++20 } }
+
+template<class T, class U>
+concept is_same = __is_same(T, U);
+
+template<class T>
+struct A {
+  template<class...>
+    requires requires { { 0 } -> is_same<T>; }
+  struct B {};
+
+  template<class...>
+    requires requires { { 1 } -> is_same<T>; }
+  static void f();
+};
+
+A<int>::B<> a1;
+A<bool>::B<> a2; // { dg-error "constraint" }
+
+int main() {
+  A<int>::f();
+  A<bool>::f(); // { dg-error "no match" }
+}
Patrick Palka March 1, 2022, 1:13 p.m. UTC | #4
On Wed, Feb 16, 2022 at 2:56 PM Patrick Palka <ppalka@redhat.com> wrote:
>
> On Tue, 15 Feb 2022, Jason Merrill wrote:
>
> > On 2/14/22 11:32, Patrick Palka wrote:
> > > Here the template context for the atomic constraint has two levels of
> > > template arguments, but since it depends only on the innermost argument
> > > T we use a single-level argument vector during substitution into the
> > > constraint (built by get_mapped_args).  We eventually pass this vector
> > > to do_auto_deduction as part of checking the return-type-requirement
> > > inside the atom, but do_auto_deduction expects outer_targs to be a full
> > > set of arguments for sake of satisfaction.
> >
> > Could we note the current number of levels in the map and use that in
> > get_mapped_args instead of the highest level parameter we happened to use?
>
> Ah yeah, that seems to work nicely.  IIUC it should suffice to remember
> whether the atomic constraint expression came from a concept definition.
> If it did, then the depth of the argument vector returned by
> get_mapped_args must be one, otherwise (as in the testcase) it must be
> the same as the template depth of the constrained entity, which is the
> depth of ARGS.
>
> How does the following look?  Bootstrapped and regtested on
> x86_64-pc-linux-gnu and also on cmcstl2 and range-v3.

Ping.

>
> -- >8 --
>
> Subject: [PATCH] c++: return-type-req in constraint using only outer tparms
>  [PR104527]
>
> Here the template context for the atomic constraint has two levels of
> template parameters, but since it depends only on the innermost parameter
> T we use a single-level argument vector (built by get_mapped_args) during
> substitution into the atom.  We eventually pass this vector to
> do_auto_deduction as part of checking the return-type-requirement within
> the atom, but do_auto_deduction expects outer_targs to be a full set of
> arguments for sake of satisfaction.
>
> This patch fixes this by making get_mapped_args always return an
> argument vector whose depth corresponds to the template depth of the
> context in which the atomic constraint expression was written, instead
> of the highest parameter level that the expression happens to use.
>
>         PR c++/104527
>
> gcc/cp/ChangeLog:
>
>         * constraint.cc (normalize_atom): Set
>         ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P appropriately.
>         (get_mapped_args):  Make static, adjust parameters.  Always
>         return a vector whose depth corresponds to the template depth of
>         the context of the atomic constraint expression.  Micro-optimize
>         by passing false as exact to safe_grow_cleared and by collapsing
>         a multi-level depth-one argument vector.
>         (satisfy_atom): Adjust call to get_mapped_args and
>         diagnose_atomic_constraint.
>         (diagnose_atomic_constraint): Replace map parameter with an args
>         parameter.
>         * cp-tree.h (ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P): Define.
>         (get_mapped_args): Remove declaration.
>
> gcc/testsuite/ChangeLog:
>
>         * g++.dg/cpp2a/concepts-return-req4.C: New test.
> ---
>  gcc/cp/constraint.cc                          | 64 +++++++++++--------
>  gcc/cp/cp-tree.h                              |  7 +-
>  .../g++.dg/cpp2a/concepts-return-req4.C       | 24 +++++++
>  3 files changed, 69 insertions(+), 26 deletions(-)
>  create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-return-req4.C
>
> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> index 12db7e5cf14..306e28955c6 100644
> --- a/gcc/cp/constraint.cc
> +++ b/gcc/cp/constraint.cc
> @@ -764,6 +764,8 @@ normalize_atom (tree t, tree args, norm_info info)
>    tree ci = build_tree_list (t, info.context);
>
>    tree atom = build1 (ATOMIC_CONSTR, ci, map);
> +  if (info.in_decl && concept_definition_p (info.in_decl))
> +    ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P (atom) = true;
>    if (!info.generate_diagnostics ())
>      {
>        /* Cache the ATOMIC_CONSTRs that we return, so that sat_hasher::equal
> @@ -2826,33 +2828,37 @@ satisfaction_value (tree t)
>      return boolean_true_node;
>  }
>
> -/* Build a new template argument list with template arguments corresponding
> -   to the parameters used in an atomic constraint.  */
> +/* Build a new template argument vector according to the parameter
> +   mapping of the atomic constraint T, using arguments from ARGS.  */
>
> -tree
> -get_mapped_args (tree map)
> +static tree
> +get_mapped_args (tree t, tree args)
>  {
> +  tree map = ATOMIC_CONSTR_MAP (t);
> +
>    /* No map, no arguments.  */
>    if (!map)
>      return NULL_TREE;
>
> -  /* Find the mapped parameter with the highest level.  */
> -  int count = 0;
> -  for (tree p = map; p; p = TREE_CHAIN (p))
> -    {
> -      int level;
> -      int index;
> -      template_parm_level_and_index (TREE_VALUE (p), &level, &index);
> -      if (level > count)
> -        count = level;
> -    }
> +  /* Determine the depth of the resulting argument vector.  */
> +  int depth;
> +  if (ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P (t))
> +    /* The expression of this atomic constraint comes from a concept definition,
> +       whose template depth is always one, so the resulting argument vector
> +       will also have depth one.  */
> +    depth = 1;
> +  else
> +    /* Otherwise, the expression of this atomic constraint was written in
> +       the context of the constrained entity, whose template depth is that
> +       of ARGS.  */
> +    depth = TMPL_ARGS_DEPTH (args);
>
>    /* Place each argument at its corresponding position in the argument
>       list. Note that the list will be sparse (not all arguments supplied),
>       but instantiation is guaranteed to only use the parameters in the
>       mapping, so null arguments would never be used.  */
> -  auto_vec< vec<tree> > lists (count);
> -  lists.quick_grow_cleared (count);
> +  auto_vec< vec<tree> > lists (depth);
> +  lists.quick_grow_cleared (depth);
>    for (tree p = map; p; p = TREE_CHAIN (p))
>      {
>        int level;
> @@ -2862,12 +2868,12 @@ get_mapped_args (tree map)
>        /* Insert the argument into its corresponding position.  */
>        vec<tree> &list = lists[level - 1];
>        if (index >= (int)list.length ())
> -       list.safe_grow_cleared (index + 1, true);
> +       list.safe_grow_cleared (index + 1, /*exact=*/false);
>        list[index] = TREE_PURPOSE (p);
>      }
>
>    /* Build the new argument list.  */
> -  tree args = make_tree_vec (lists.length ());
> +  args = make_tree_vec (lists.length ());
>    for (unsigned i = 0; i != lists.length (); ++i)
>      {
>        vec<tree> &list = lists[i];
> @@ -2879,6 +2885,16 @@ get_mapped_args (tree map)
>      }
>    SET_NON_DEFAULT_TEMPLATE_ARGS_COUNT (args, 0);
>
> +  if (TMPL_ARGS_HAVE_MULTIPLE_LEVELS (args)
> +      && TMPL_ARGS_DEPTH (args) == 1)
> +    {
> +      /* Micro-optimization: represent a depth-one argument vector
> +        using a single level.  */
> +      tree level = TMPL_ARGS_LEVEL (args, 1);
> +      ggc_free (args);
> +      args = level;
> +    }
> +
>    return args;
>  }
>
> @@ -2933,7 +2949,7 @@ satisfy_atom (tree t, tree args, sat_info info)
>      }
>
>    /* Rebuild the argument vector from the parameter mapping.  */
> -  args = get_mapped_args (map);
> +  args = get_mapped_args (t, args);
>
>    /* Apply the parameter mapping (i.e., just substitute).  */
>    tree expr = ATOMIC_CONSTR_EXPR (t);
> @@ -2955,7 +2971,7 @@ satisfy_atom (tree t, tree args, sat_info info)
>    if (!same_type_p (TREE_TYPE (result), boolean_type_node))
>      {
>        if (info.noisy ())
> -       diagnose_atomic_constraint (t, map, result, info);
> +       diagnose_atomic_constraint (t, args, result, info);
>        return cache.save (inst_cache.save (error_mark_node));
>      }
>
> @@ -2974,7 +2990,7 @@ satisfy_atom (tree t, tree args, sat_info info)
>      }
>    result = satisfaction_value (result);
>    if (result == boolean_false_node && info.diagnose_unsatisfaction_p ())
> -    diagnose_atomic_constraint (t, map, result, info);
> +    diagnose_atomic_constraint (t, args, result, info);
>
>    return cache.save (inst_cache.save (result));
>  }
> @@ -3642,11 +3658,10 @@ diagnose_trait_expr (tree expr, tree args)
>      }
>  }
>
> -/* Diagnose a substitution failure in the atomic constraint T when applied
> -   with the instantiated parameter mapping MAP.  */
> +/* Diagnose a substitution failure in the atomic constraint T using ARGS.  */
>
>  static void
> -diagnose_atomic_constraint (tree t, tree map, tree result, sat_info info)
> +diagnose_atomic_constraint (tree t, tree args, tree result, sat_info info)
>  {
>    /* If the constraint is already ill-formed, we've previously diagnosed
>       the reason. We should still say why the constraints aren't satisfied.  */
> @@ -3667,7 +3682,6 @@ diagnose_atomic_constraint (tree t, tree map, tree result, sat_info info)
>    /* Generate better diagnostics for certain kinds of expressions.  */
>    tree expr = ATOMIC_CONSTR_EXPR (t);
>    STRIP_ANY_LOCATION_WRAPPER (expr);
> -  tree args = get_mapped_args (map);
>    switch (TREE_CODE (expr))
>      {
>      case TRAIT_EXPR:
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index f253b32c3f2..dc2429a8406 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -466,6 +466,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];
>        IMPLICIT_CONV_EXPR_NONTYPE_ARG (in IMPLICIT_CONV_EXPR)
>        BASELINK_FUNCTIONS_MAYBE_INCOMPLETE_P (in BASELINK)
>        BIND_EXPR_VEC_DTOR (in BIND_EXPR)
> +      ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P (in ATOMIC_CONSTR)
>     2: IDENTIFIER_KIND_BIT_2 (in IDENTIFIER_NODE)
>        ICS_THIS_FLAG (in _CONV)
>        DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (in VAR_DECL)
> @@ -1679,6 +1680,11 @@ check_constraint_info (tree t)
>  #define ATOMIC_CONSTR_MAP_INSTANTIATED_P(NODE) \
>    TREE_LANG_FLAG_0 (ATOMIC_CONSTR_CHECK (NODE))
>
> +/* Whether the expression for this atomic constraint belongs to a
> +   concept definition.  */
> +#define ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P(NODE) \
> +  TREE_LANG_FLAG_1 (ATOMIC_CONSTR_CHECK (NODE))
> +
>  /* The expression of an atomic constraint. */
>  #define ATOMIC_CONSTR_EXPR(NODE) \
>    CONSTR_EXPR (ATOMIC_CONSTR_CHECK (NODE))
> @@ -8306,7 +8312,6 @@ extern tree evaluate_requires_expr                (tree);
>  extern tree tsubst_constraint                   (tree, tree, tsubst_flags_t, tree);
>  extern tree tsubst_constraint_info              (tree, tree, tsubst_flags_t, tree);
>  extern tree tsubst_parameter_mapping           (tree, tree, tsubst_flags_t, tree);
> -extern tree get_mapped_args                    (tree);
>
>  struct processing_constraint_expression_sentinel
>  {
> diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-return-req4.C b/gcc/testsuite/g++.dg/cpp2a/concepts-return-req4.C
> new file mode 100644
> index 00000000000..471946bc8eb
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-return-req4.C
> @@ -0,0 +1,24 @@
> +// PR c++/104527
> +// { dg-do compile { target c++20 } }
> +
> +template<class T, class U>
> +concept is_same = __is_same(T, U);
> +
> +template<class T>
> +struct A {
> +  template<class...>
> +    requires requires { { 0 } -> is_same<T>; }
> +  struct B {};
> +
> +  template<class...>
> +    requires requires { { 1 } -> is_same<T>; }
> +  static void f();
> +};
> +
> +A<int>::B<> a1;
> +A<bool>::B<> a2; // { dg-error "constraint" }
> +
> +int main() {
> +  A<int>::f();
> +  A<bool>::f(); // { dg-error "no match" }
> +}
> --
> 2.35.1.129.gb80121027d
>
Jason Merrill March 10, 2022, 7:43 p.m. UTC | #5
On 2/16/22 15:56, Patrick Palka wrote:
> On Tue, 15 Feb 2022, Jason Merrill wrote:
> 
>> On 2/14/22 11:32, Patrick Palka wrote:
>>> Here the template context for the atomic constraint has two levels of
>>> template arguments, but since it depends only on the innermost argument
>>> T we use a single-level argument vector during substitution into the
>>> constraint (built by get_mapped_args).  We eventually pass this vector
>>> to do_auto_deduction as part of checking the return-type-requirement
>>> inside the atom, but do_auto_deduction expects outer_targs to be a full
>>> set of arguments for sake of satisfaction.
>>
>> Could we note the current number of levels in the map and use that in
>> get_mapped_args instead of the highest level parameter we happened to use?
> 
> Ah yeah, that seems to work nicely.  IIUC it should suffice to remember
> whether the atomic constraint expression came from a concept definition.
> If it did, then the depth of the argument vector returned by
> get_mapped_args must be one, otherwise (as in the testcase) it must be
> the same as the template depth of the constrained entity, which is the
> depth of ARGS.
> 
> How does the following look?  Bootstrapped and regtested on
> x86_64-pc-linux-gnu and also on cmcstl2 and range-v3.
> 
> -- >8 --
> 
> Subject: [PATCH] c++: return-type-req in constraint using only outer tparms
>   [PR104527]
> 
> Here the template context for the atomic constraint has two levels of
> template parameters, but since it depends only on the innermost parameter
> T we use a single-level argument vector (built by get_mapped_args) during
> substitution into the atom.  We eventually pass this vector to
> do_auto_deduction as part of checking the return-type-requirement within
> the atom, but do_auto_deduction expects outer_targs to be a full set of
> arguments for sake of satisfaction.
> 
> This patch fixes this by making get_mapped_args always return an
> argument vector whose depth corresponds to the template depth of the
> context in which the atomic constraint expression was written, instead
> of the highest parameter level that the expression happens to use.
> 
> 	PR c++/104527
> 
> gcc/cp/ChangeLog:
> 
> 	* constraint.cc (normalize_atom): Set
> 	ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P appropriately.
> 	(get_mapped_args):  Make static, adjust parameters.  Always
> 	return a vector whose depth corresponds to the template depth of
> 	the context of the atomic constraint expression.  Micro-optimize
> 	by passing false as exact to safe_grow_cleared and by collapsing
> 	a multi-level depth-one argument vector.
> 	(satisfy_atom): Adjust call to get_mapped_args and
> 	diagnose_atomic_constraint.
> 	(diagnose_atomic_constraint): Replace map parameter with an args
> 	parameter.
> 	* cp-tree.h (ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P): Define.
> 	(get_mapped_args): Remove declaration.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/cpp2a/concepts-return-req4.C: New test.
> ---
>   gcc/cp/constraint.cc                          | 64 +++++++++++--------
>   gcc/cp/cp-tree.h                              |  7 +-
>   .../g++.dg/cpp2a/concepts-return-req4.C       | 24 +++++++
>   3 files changed, 69 insertions(+), 26 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-return-req4.C
> 
> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> index 12db7e5cf14..306e28955c6 100644
> --- a/gcc/cp/constraint.cc
> +++ b/gcc/cp/constraint.cc
> @@ -764,6 +764,8 @@ normalize_atom (tree t, tree args, norm_info info)
>     tree ci = build_tree_list (t, info.context);
>   
>     tree atom = build1 (ATOMIC_CONSTR, ci, map);
> +  if (info.in_decl && concept_definition_p (info.in_decl))
> +    ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P (atom) = true;

I'm a bit nervous about relying on in_decl, given that we support 
normalizing when it isn't set; I don't remember the circumstances for 
that.  Maybe make the flag indicate that ctx_parms had depth 1?

>     if (!info.generate_diagnostics ())
>       {
>         /* Cache the ATOMIC_CONSTRs that we return, so that sat_hasher::equal
> @@ -2826,33 +2828,37 @@ satisfaction_value (tree t)
>       return boolean_true_node;
>   }
>   
> -/* Build a new template argument list with template arguments corresponding
> -   to the parameters used in an atomic constraint.  */
> +/* Build a new template argument vector according to the parameter
> +   mapping of the atomic constraint T, using arguments from ARGS.  */
>   
> -tree
> -get_mapped_args (tree map)
> +static tree
> +get_mapped_args (tree t, tree args)
>   {
> +  tree map = ATOMIC_CONSTR_MAP (t);
> +
>     /* No map, no arguments.  */
>     if (!map)
>       return NULL_TREE;
>   
> -  /* Find the mapped parameter with the highest level.  */
> -  int count = 0;
> -  for (tree p = map; p; p = TREE_CHAIN (p))
> -    {
> -      int level;
> -      int index;
> -      template_parm_level_and_index (TREE_VALUE (p), &level, &index);
> -      if (level > count)
> -        count = level;
> -    }
> +  /* Determine the depth of the resulting argument vector.  */
> +  int depth;
> +  if (ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P (t))
> +    /* The expression of this atomic constraint comes from a concept definition,
> +       whose template depth is always one, so the resulting argument vector
> +       will also have depth one.  */
> +    depth = 1;
> +  else
> +    /* Otherwise, the expression of this atomic constraint was written in
> +       the context of the constrained entity, whose template depth is that
> +       of ARGS.  */
> +    depth = TMPL_ARGS_DEPTH (args);
>   
>     /* Place each argument at its corresponding position in the argument
>        list. Note that the list will be sparse (not all arguments supplied),
>        but instantiation is guaranteed to only use the parameters in the
>        mapping, so null arguments would never be used.  */
> -  auto_vec< vec<tree> > lists (count);
> -  lists.quick_grow_cleared (count);
> +  auto_vec< vec<tree> > lists (depth);
> +  lists.quick_grow_cleared (depth);
>     for (tree p = map; p; p = TREE_CHAIN (p))
>       {
>         int level;
> @@ -2862,12 +2868,12 @@ get_mapped_args (tree map)
>         /* Insert the argument into its corresponding position.  */
>         vec<tree> &list = lists[level - 1];
>         if (index >= (int)list.length ())
> -	list.safe_grow_cleared (index + 1, true);
> +	list.safe_grow_cleared (index + 1, /*exact=*/false);
>         list[index] = TREE_PURPOSE (p);
>       }
>   
>     /* Build the new argument list.  */
> -  tree args = make_tree_vec (lists.length ());
> +  args = make_tree_vec (lists.length ());
>     for (unsigned i = 0; i != lists.length (); ++i)
>       {
>         vec<tree> &list = lists[i];
> @@ -2879,6 +2885,16 @@ get_mapped_args (tree map)
>       }
>     SET_NON_DEFAULT_TEMPLATE_ARGS_COUNT (args, 0);
>   
> +  if (TMPL_ARGS_HAVE_MULTIPLE_LEVELS (args)
> +      && TMPL_ARGS_DEPTH (args) == 1)
> +    {
> +      /* Micro-optimization: represent a depth-one argument vector
> +	 using a single level.  */
> +      tree level = TMPL_ARGS_LEVEL (args, 1);
> +      ggc_free (args);
> +      args = level;
> +    }
> +
>     return args;
>   }
>   
> @@ -2933,7 +2949,7 @@ satisfy_atom (tree t, tree args, sat_info info)
>       }
>   
>     /* Rebuild the argument vector from the parameter mapping.  */
> -  args = get_mapped_args (map);
> +  args = get_mapped_args (t, args);
>   
>     /* Apply the parameter mapping (i.e., just substitute).  */
>     tree expr = ATOMIC_CONSTR_EXPR (t);
> @@ -2955,7 +2971,7 @@ satisfy_atom (tree t, tree args, sat_info info)
>     if (!same_type_p (TREE_TYPE (result), boolean_type_node))
>       {
>         if (info.noisy ())
> -	diagnose_atomic_constraint (t, map, result, info);
> +	diagnose_atomic_constraint (t, args, result, info);
>         return cache.save (inst_cache.save (error_mark_node));
>       }
>   
> @@ -2974,7 +2990,7 @@ satisfy_atom (tree t, tree args, sat_info info)
>       }
>     result = satisfaction_value (result);
>     if (result == boolean_false_node && info.diagnose_unsatisfaction_p ())
> -    diagnose_atomic_constraint (t, map, result, info);
> +    diagnose_atomic_constraint (t, args, result, info);
>   
>     return cache.save (inst_cache.save (result));
>   }
> @@ -3642,11 +3658,10 @@ diagnose_trait_expr (tree expr, tree args)
>       }
>   }
>   
> -/* Diagnose a substitution failure in the atomic constraint T when applied
> -   with the instantiated parameter mapping MAP.  */
> +/* Diagnose a substitution failure in the atomic constraint T using ARGS.  */
>   
>   static void
> -diagnose_atomic_constraint (tree t, tree map, tree result, sat_info info)
> +diagnose_atomic_constraint (tree t, tree args, tree result, sat_info info)
>   {
>     /* If the constraint is already ill-formed, we've previously diagnosed
>        the reason. We should still say why the constraints aren't satisfied.  */
> @@ -3667,7 +3682,6 @@ diagnose_atomic_constraint (tree t, tree map, tree result, sat_info info)
>     /* Generate better diagnostics for certain kinds of expressions.  */
>     tree expr = ATOMIC_CONSTR_EXPR (t);
>     STRIP_ANY_LOCATION_WRAPPER (expr);
> -  tree args = get_mapped_args (map);
>     switch (TREE_CODE (expr))
>       {
>       case TRAIT_EXPR:
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index f253b32c3f2..dc2429a8406 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -466,6 +466,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];
>         IMPLICIT_CONV_EXPR_NONTYPE_ARG (in IMPLICIT_CONV_EXPR)
>         BASELINK_FUNCTIONS_MAYBE_INCOMPLETE_P (in BASELINK)
>         BIND_EXPR_VEC_DTOR (in BIND_EXPR)
> +      ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P (in ATOMIC_CONSTR)
>      2: IDENTIFIER_KIND_BIT_2 (in IDENTIFIER_NODE)
>         ICS_THIS_FLAG (in _CONV)
>         DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (in VAR_DECL)
> @@ -1679,6 +1680,11 @@ check_constraint_info (tree t)
>   #define ATOMIC_CONSTR_MAP_INSTANTIATED_P(NODE) \
>     TREE_LANG_FLAG_0 (ATOMIC_CONSTR_CHECK (NODE))
>   
> +/* Whether the expression for this atomic constraint belongs to a
> +   concept definition.  */
> +#define ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P(NODE) \
> +  TREE_LANG_FLAG_1 (ATOMIC_CONSTR_CHECK (NODE))
> +
>   /* The expression of an atomic constraint. */
>   #define ATOMIC_CONSTR_EXPR(NODE) \
>     CONSTR_EXPR (ATOMIC_CONSTR_CHECK (NODE))
> @@ -8306,7 +8312,6 @@ extern tree evaluate_requires_expr		(tree);
>   extern tree tsubst_constraint                   (tree, tree, tsubst_flags_t, tree);
>   extern tree tsubst_constraint_info              (tree, tree, tsubst_flags_t, tree);
>   extern tree tsubst_parameter_mapping		(tree, tree, tsubst_flags_t, tree);
> -extern tree get_mapped_args			(tree);
>   
>   struct processing_constraint_expression_sentinel
>   {
> diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-return-req4.C b/gcc/testsuite/g++.dg/cpp2a/concepts-return-req4.C
> new file mode 100644
> index 00000000000..471946bc8eb
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-return-req4.C
> @@ -0,0 +1,24 @@
> +// PR c++/104527
> +// { dg-do compile { target c++20 } }
> +
> +template<class T, class U>
> +concept is_same = __is_same(T, U);
> +
> +template<class T>
> +struct A {
> +  template<class...>
> +    requires requires { { 0 } -> is_same<T>; }
> +  struct B {};
> +
> +  template<class...>
> +    requires requires { { 1 } -> is_same<T>; }
> +  static void f();
> +};
> +
> +A<int>::B<> a1;
> +A<bool>::B<> a2; // { dg-error "constraint" }
> +
> +int main() {
> +  A<int>::f();
> +  A<bool>::f(); // { dg-error "no match" }
> +}
Patrick Palka March 10, 2022, 8:57 p.m. UTC | #6
On Thu, 10 Mar 2022, Jason Merrill wrote:

> On 2/16/22 15:56, Patrick Palka wrote:
> > On Tue, 15 Feb 2022, Jason Merrill wrote:
> > 
> > > On 2/14/22 11:32, Patrick Palka wrote:
> > > > Here the template context for the atomic constraint has two levels of
> > > > template arguments, but since it depends only on the innermost argument
> > > > T we use a single-level argument vector during substitution into the
> > > > constraint (built by get_mapped_args).  We eventually pass this vector
> > > > to do_auto_deduction as part of checking the return-type-requirement
> > > > inside the atom, but do_auto_deduction expects outer_targs to be a full
> > > > set of arguments for sake of satisfaction.
> > > 
> > > Could we note the current number of levels in the map and use that in
> > > get_mapped_args instead of the highest level parameter we happened to use?
> > 
> > Ah yeah, that seems to work nicely.  IIUC it should suffice to remember
> > whether the atomic constraint expression came from a concept definition.
> > If it did, then the depth of the argument vector returned by
> > get_mapped_args must be one, otherwise (as in the testcase) it must be
> > the same as the template depth of the constrained entity, which is the
> > depth of ARGS.
> > 
> > How does the following look?  Bootstrapped and regtested on
> > x86_64-pc-linux-gnu and also on cmcstl2 and range-v3.
> > 
> > -- >8 --
> > 
> > Subject: [PATCH] c++: return-type-req in constraint using only outer tparms
> >   [PR104527]
> > 
> > Here the template context for the atomic constraint has two levels of
> > template parameters, but since it depends only on the innermost parameter
> > T we use a single-level argument vector (built by get_mapped_args) during
> > substitution into the atom.  We eventually pass this vector to
> > do_auto_deduction as part of checking the return-type-requirement within
> > the atom, but do_auto_deduction expects outer_targs to be a full set of
> > arguments for sake of satisfaction.
> > 
> > This patch fixes this by making get_mapped_args always return an
> > argument vector whose depth corresponds to the template depth of the
> > context in which the atomic constraint expression was written, instead
> > of the highest parameter level that the expression happens to use.
> > 
> > 	PR c++/104527
> > 
> > gcc/cp/ChangeLog:
> > 
> > 	* constraint.cc (normalize_atom): Set
> > 	ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P appropriately.
> > 	(get_mapped_args):  Make static, adjust parameters.  Always
> > 	return a vector whose depth corresponds to the template depth of
> > 	the context of the atomic constraint expression.  Micro-optimize
> > 	by passing false as exact to safe_grow_cleared and by collapsing
> > 	a multi-level depth-one argument vector.
> > 	(satisfy_atom): Adjust call to get_mapped_args and
> > 	diagnose_atomic_constraint.
> > 	(diagnose_atomic_constraint): Replace map parameter with an args
> > 	parameter.
> > 	* cp-tree.h (ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P): Define.
> > 	(get_mapped_args): Remove declaration.
> > 
> > gcc/testsuite/ChangeLog:
> > 
> > 	* g++.dg/cpp2a/concepts-return-req4.C: New test.
> > ---
> >   gcc/cp/constraint.cc                          | 64 +++++++++++--------
> >   gcc/cp/cp-tree.h                              |  7 +-
> >   .../g++.dg/cpp2a/concepts-return-req4.C       | 24 +++++++
> >   3 files changed, 69 insertions(+), 26 deletions(-)
> >   create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-return-req4.C
> > 
> > diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> > index 12db7e5cf14..306e28955c6 100644
> > --- a/gcc/cp/constraint.cc
> > +++ b/gcc/cp/constraint.cc
> > @@ -764,6 +764,8 @@ normalize_atom (tree t, tree args, norm_info info)
> >     tree ci = build_tree_list (t, info.context);
> >       tree atom = build1 (ATOMIC_CONSTR, ci, map);
> > +  if (info.in_decl && concept_definition_p (info.in_decl))
> > +    ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P (atom) = true;
> 
> I'm a bit nervous about relying on in_decl, given that we support normalizing
> when it isn't set; I don't remember the circumstances for that.  Maybe make
> the flag indicate that ctx_parms had depth 1?

in_decl gets reliably updated by norm_info::update_context whenever we
recurse inside a concept-id during normalization.  And I think the only
other situation we have to worry about is when starting out with a
concept-id, which is handled by normalize_concept_definition where we
also set in_decl appropriately.

AFAICT, in_decl is not set (at the start) only when normalizing a
placeholder type constraint or nested-requirement, and from some
subsumption entrypoints.  And we shouldn't see an atom that belongs to a
concept in these cases unless we recurse into a concept-id, in which
case norm_info::update_context will update in_decl appropriately.

So IMHO it should be safe to rely on in_decl here to detect if the atom
belongs to a concept, at least given the current entrypoints to
subsumption/satisfaction..

> 
> >     if (!info.generate_diagnostics ())
> >       {
> >         /* Cache the ATOMIC_CONSTRs that we return, so that
> > sat_hasher::equal
> > @@ -2826,33 +2828,37 @@ satisfaction_value (tree t)
> >       return boolean_true_node;
> >   }
> >   -/* Build a new template argument list with template arguments
> > corresponding
> > -   to the parameters used in an atomic constraint.  */
> > +/* Build a new template argument vector according to the parameter
> > +   mapping of the atomic constraint T, using arguments from ARGS.  */
> >   -tree
> > -get_mapped_args (tree map)
> > +static tree
> > +get_mapped_args (tree t, tree args)
> >   {
> > +  tree map = ATOMIC_CONSTR_MAP (t);
> > +
> >     /* No map, no arguments.  */
> >     if (!map)
> >       return NULL_TREE;
> >   -  /* Find the mapped parameter with the highest level.  */
> > -  int count = 0;
> > -  for (tree p = map; p; p = TREE_CHAIN (p))
> > -    {
> > -      int level;
> > -      int index;
> > -      template_parm_level_and_index (TREE_VALUE (p), &level, &index);
> > -      if (level > count)
> > -        count = level;
> > -    }
> > +  /* Determine the depth of the resulting argument vector.  */
> > +  int depth;
> > +  if (ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P (t))
> > +    /* The expression of this atomic constraint comes from a concept
> > definition,
> > +       whose template depth is always one, so the resulting argument vector
> > +       will also have depth one.  */
> > +    depth = 1;
> > +  else
> > +    /* Otherwise, the expression of this atomic constraint was written in
> > +       the context of the constrained entity, whose template depth is that
> > +       of ARGS.  */
> > +    depth = TMPL_ARGS_DEPTH (args);
> >       /* Place each argument at its corresponding position in the argument
> >        list. Note that the list will be sparse (not all arguments supplied),
> >        but instantiation is guaranteed to only use the parameters in the
> >        mapping, so null arguments would never be used.  */
> > -  auto_vec< vec<tree> > lists (count);
> > -  lists.quick_grow_cleared (count);
> > +  auto_vec< vec<tree> > lists (depth);
> > +  lists.quick_grow_cleared (depth);
> >     for (tree p = map; p; p = TREE_CHAIN (p))
> >       {
> >         int level;
> > @@ -2862,12 +2868,12 @@ get_mapped_args (tree map)
> >         /* Insert the argument into its corresponding position.  */
> >         vec<tree> &list = lists[level - 1];
> >         if (index >= (int)list.length ())
> > -	list.safe_grow_cleared (index + 1, true);
> > +	list.safe_grow_cleared (index + 1, /*exact=*/false);
> >         list[index] = TREE_PURPOSE (p);
> >       }
> >       /* Build the new argument list.  */
> > -  tree args = make_tree_vec (lists.length ());
> > +  args = make_tree_vec (lists.length ());
> >     for (unsigned i = 0; i != lists.length (); ++i)
> >       {
> >         vec<tree> &list = lists[i];
> > @@ -2879,6 +2885,16 @@ get_mapped_args (tree map)
> >       }
> >     SET_NON_DEFAULT_TEMPLATE_ARGS_COUNT (args, 0);
> >   +  if (TMPL_ARGS_HAVE_MULTIPLE_LEVELS (args)
> > +      && TMPL_ARGS_DEPTH (args) == 1)
> > +    {
> > +      /* Micro-optimization: represent a depth-one argument vector
> > +	 using a single level.  */
> > +      tree level = TMPL_ARGS_LEVEL (args, 1);
> > +      ggc_free (args);
> > +      args = level;
> > +    }
> > +
> >     return args;
> >   }
> >   @@ -2933,7 +2949,7 @@ satisfy_atom (tree t, tree args, sat_info info)
> >       }
> >       /* Rebuild the argument vector from the parameter mapping.  */
> > -  args = get_mapped_args (map);
> > +  args = get_mapped_args (t, args);
> >       /* Apply the parameter mapping (i.e., just substitute).  */
> >     tree expr = ATOMIC_CONSTR_EXPR (t);
> > @@ -2955,7 +2971,7 @@ satisfy_atom (tree t, tree args, sat_info info)
> >     if (!same_type_p (TREE_TYPE (result), boolean_type_node))
> >       {
> >         if (info.noisy ())
> > -	diagnose_atomic_constraint (t, map, result, info);
> > +	diagnose_atomic_constraint (t, args, result, info);
> >         return cache.save (inst_cache.save (error_mark_node));
> >       }
> >   @@ -2974,7 +2990,7 @@ satisfy_atom (tree t, tree args, sat_info info)
> >       }
> >     result = satisfaction_value (result);
> >     if (result == boolean_false_node && info.diagnose_unsatisfaction_p ())
> > -    diagnose_atomic_constraint (t, map, result, info);
> > +    diagnose_atomic_constraint (t, args, result, info);
> >       return cache.save (inst_cache.save (result));
> >   }
> > @@ -3642,11 +3658,10 @@ diagnose_trait_expr (tree expr, tree args)
> >       }
> >   }
> >   -/* Diagnose a substitution failure in the atomic constraint T when
> > applied
> > -   with the instantiated parameter mapping MAP.  */
> > +/* Diagnose a substitution failure in the atomic constraint T using ARGS.
> > */
> >     static void
> > -diagnose_atomic_constraint (tree t, tree map, tree result, sat_info info)
> > +diagnose_atomic_constraint (tree t, tree args, tree result, sat_info info)
> >   {
> >     /* If the constraint is already ill-formed, we've previously diagnosed
> >        the reason. We should still say why the constraints aren't satisfied.
> > */
> > @@ -3667,7 +3682,6 @@ diagnose_atomic_constraint (tree t, tree map, tree
> > result, sat_info info)
> >     /* Generate better diagnostics for certain kinds of expressions.  */
> >     tree expr = ATOMIC_CONSTR_EXPR (t);
> >     STRIP_ANY_LOCATION_WRAPPER (expr);
> > -  tree args = get_mapped_args (map);
> >     switch (TREE_CODE (expr))
> >       {
> >       case TRAIT_EXPR:
> > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> > index f253b32c3f2..dc2429a8406 100644
> > --- a/gcc/cp/cp-tree.h
> > +++ b/gcc/cp/cp-tree.h
> > @@ -466,6 +466,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];
> >         IMPLICIT_CONV_EXPR_NONTYPE_ARG (in IMPLICIT_CONV_EXPR)
> >         BASELINK_FUNCTIONS_MAYBE_INCOMPLETE_P (in BASELINK)
> >         BIND_EXPR_VEC_DTOR (in BIND_EXPR)
> > +      ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P (in ATOMIC_CONSTR)
> >      2: IDENTIFIER_KIND_BIT_2 (in IDENTIFIER_NODE)
> >         ICS_THIS_FLAG (in _CONV)
> >         DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (in VAR_DECL)
> > @@ -1679,6 +1680,11 @@ check_constraint_info (tree t)
> >   #define ATOMIC_CONSTR_MAP_INSTANTIATED_P(NODE) \
> >     TREE_LANG_FLAG_0 (ATOMIC_CONSTR_CHECK (NODE))
> >   +/* Whether the expression for this atomic constraint belongs to a
> > +   concept definition.  */
> > +#define ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P(NODE) \
> > +  TREE_LANG_FLAG_1 (ATOMIC_CONSTR_CHECK (NODE))
> > +
> >   /* The expression of an atomic constraint. */
> >   #define ATOMIC_CONSTR_EXPR(NODE) \
> >     CONSTR_EXPR (ATOMIC_CONSTR_CHECK (NODE))
> > @@ -8306,7 +8312,6 @@ extern tree evaluate_requires_expr
> > (tree);
> >   extern tree tsubst_constraint                   (tree, tree,
> > tsubst_flags_t, tree);
> >   extern tree tsubst_constraint_info              (tree, tree,
> > tsubst_flags_t, tree);
> >   extern tree tsubst_parameter_mapping		(tree, tree,
> > tsubst_flags_t, tree);
> > -extern tree get_mapped_args			(tree);
> >     struct processing_constraint_expression_sentinel
> >   {
> > diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-return-req4.C
> > b/gcc/testsuite/g++.dg/cpp2a/concepts-return-req4.C
> > new file mode 100644
> > index 00000000000..471946bc8eb
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-return-req4.C
> > @@ -0,0 +1,24 @@
> > +// PR c++/104527
> > +// { dg-do compile { target c++20 } }
> > +
> > +template<class T, class U>
> > +concept is_same = __is_same(T, U);
> > +
> > +template<class T>
> > +struct A {
> > +  template<class...>
> > +    requires requires { { 0 } -> is_same<T>; }
> > +  struct B {};
> > +
> > +  template<class...>
> > +    requires requires { { 1 } -> is_same<T>; }
> > +  static void f();
> > +};
> > +
> > +A<int>::B<> a1;
> > +A<bool>::B<> a2; // { dg-error "constraint" }
> > +
> > +int main() {
> > +  A<int>::f();
> > +  A<bool>::f(); // { dg-error "no match" }
> > +}
> 
>
Jason Merrill March 11, 2022, 10:19 p.m. UTC | #7
On 3/10/22 16:57, Patrick Palka wrote:
> 
> On Thu, 10 Mar 2022, Jason Merrill wrote:
> 
>> On 2/16/22 15:56, Patrick Palka wrote:
>>> On Tue, 15 Feb 2022, Jason Merrill wrote:
>>>
>>>> On 2/14/22 11:32, Patrick Palka wrote:
>>>>> Here the template context for the atomic constraint has two levels of
>>>>> template arguments, but since it depends only on the innermost argument
>>>>> T we use a single-level argument vector during substitution into the
>>>>> constraint (built by get_mapped_args).  We eventually pass this vector
>>>>> to do_auto_deduction as part of checking the return-type-requirement
>>>>> inside the atom, but do_auto_deduction expects outer_targs to be a full
>>>>> set of arguments for sake of satisfaction.
>>>>
>>>> Could we note the current number of levels in the map and use that in
>>>> get_mapped_args instead of the highest level parameter we happened to use?
>>>
>>> Ah yeah, that seems to work nicely.  IIUC it should suffice to remember
>>> whether the atomic constraint expression came from a concept definition.
>>> If it did, then the depth of the argument vector returned by
>>> get_mapped_args must be one, otherwise (as in the testcase) it must be
>>> the same as the template depth of the constrained entity, which is the
>>> depth of ARGS.
>>>
>>> How does the following look?  Bootstrapped and regtested on
>>> x86_64-pc-linux-gnu and also on cmcstl2 and range-v3.
>>>
>>> -- >8 --
>>>
>>> Subject: [PATCH] c++: return-type-req in constraint using only outer tparms
>>>    [PR104527]
>>>
>>> Here the template context for the atomic constraint has two levels of
>>> template parameters, but since it depends only on the innermost parameter
>>> T we use a single-level argument vector (built by get_mapped_args) during
>>> substitution into the atom.  We eventually pass this vector to
>>> do_auto_deduction as part of checking the return-type-requirement within
>>> the atom, but do_auto_deduction expects outer_targs to be a full set of
>>> arguments for sake of satisfaction.
>>>
>>> This patch fixes this by making get_mapped_args always return an
>>> argument vector whose depth corresponds to the template depth of the
>>> context in which the atomic constraint expression was written, instead
>>> of the highest parameter level that the expression happens to use.
>>>
>>> 	PR c++/104527
>>>
>>> gcc/cp/ChangeLog:
>>>
>>> 	* constraint.cc (normalize_atom): Set
>>> 	ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P appropriately.
>>> 	(get_mapped_args):  Make static, adjust parameters.  Always
>>> 	return a vector whose depth corresponds to the template depth of
>>> 	the context of the atomic constraint expression.  Micro-optimize
>>> 	by passing false as exact to safe_grow_cleared and by collapsing
>>> 	a multi-level depth-one argument vector.
>>> 	(satisfy_atom): Adjust call to get_mapped_args and
>>> 	diagnose_atomic_constraint.
>>> 	(diagnose_atomic_constraint): Replace map parameter with an args
>>> 	parameter.
>>> 	* cp-tree.h (ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P): Define.
>>> 	(get_mapped_args): Remove declaration.
>>>
>>> gcc/testsuite/ChangeLog:
>>>
>>> 	* g++.dg/cpp2a/concepts-return-req4.C: New test.
>>> ---
>>>    gcc/cp/constraint.cc                          | 64 +++++++++++--------
>>>    gcc/cp/cp-tree.h                              |  7 +-
>>>    .../g++.dg/cpp2a/concepts-return-req4.C       | 24 +++++++
>>>    3 files changed, 69 insertions(+), 26 deletions(-)
>>>    create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-return-req4.C
>>>
>>> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
>>> index 12db7e5cf14..306e28955c6 100644
>>> --- a/gcc/cp/constraint.cc
>>> +++ b/gcc/cp/constraint.cc
>>> @@ -764,6 +764,8 @@ normalize_atom (tree t, tree args, norm_info info)
>>>      tree ci = build_tree_list (t, info.context);
>>>        tree atom = build1 (ATOMIC_CONSTR, ci, map);
>>> +  if (info.in_decl && concept_definition_p (info.in_decl))
>>> +    ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P (atom) = true;
>>
>> I'm a bit nervous about relying on in_decl, given that we support normalizing
>> when it isn't set; I don't remember the circumstances for that.  Maybe make
>> the flag indicate that ctx_parms had depth 1?
> 
> in_decl gets reliably updated by norm_info::update_context whenever we
> recurse inside a concept-id during normalization.  And I think the only
> other situation we have to worry about is when starting out with a
> concept-id, which is handled by normalize_concept_definition where we
> also set in_decl appropriately.
> 
> AFAICT, in_decl is not set (at the start) only when normalizing a
> placeholder type constraint or nested-requirement, and from some
> subsumption entrypoints.  And we shouldn't see an atom that belongs to a
> concept in these cases unless we recurse into a concept-id, in which
> case norm_info::update_context will update in_decl appropriately.
> 
> So IMHO it should be safe to rely on in_decl here to detect if the atom
> belongs to a concept, at least given the current entrypoints to
> subsumption/satisfaction..

Sounds good; please put a bit of that explanation in a comment where you 
set the flag.  OK with that change.

>>
>>>      if (!info.generate_diagnostics ())
>>>        {
>>>          /* Cache the ATOMIC_CONSTRs that we return, so that
>>> sat_hasher::equal
>>> @@ -2826,33 +2828,37 @@ satisfaction_value (tree t)
>>>        return boolean_true_node;
>>>    }
>>>    -/* Build a new template argument list with template arguments
>>> corresponding
>>> -   to the parameters used in an atomic constraint.  */
>>> +/* Build a new template argument vector according to the parameter
>>> +   mapping of the atomic constraint T, using arguments from ARGS.  */
>>>    -tree
>>> -get_mapped_args (tree map)
>>> +static tree
>>> +get_mapped_args (tree t, tree args)
>>>    {
>>> +  tree map = ATOMIC_CONSTR_MAP (t);
>>> +
>>>      /* No map, no arguments.  */
>>>      if (!map)
>>>        return NULL_TREE;
>>>    -  /* Find the mapped parameter with the highest level.  */
>>> -  int count = 0;
>>> -  for (tree p = map; p; p = TREE_CHAIN (p))
>>> -    {
>>> -      int level;
>>> -      int index;
>>> -      template_parm_level_and_index (TREE_VALUE (p), &level, &index);
>>> -      if (level > count)
>>> -        count = level;
>>> -    }
>>> +  /* Determine the depth of the resulting argument vector.  */
>>> +  int depth;
>>> +  if (ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P (t))
>>> +    /* The expression of this atomic constraint comes from a concept
>>> definition,
>>> +       whose template depth is always one, so the resulting argument vector
>>> +       will also have depth one.  */
>>> +    depth = 1;
>>> +  else
>>> +    /* Otherwise, the expression of this atomic constraint was written in
>>> +       the context of the constrained entity, whose template depth is that
>>> +       of ARGS.  */
>>> +    depth = TMPL_ARGS_DEPTH (args);
>>>        /* Place each argument at its corresponding position in the argument
>>>         list. Note that the list will be sparse (not all arguments supplied),
>>>         but instantiation is guaranteed to only use the parameters in the
>>>         mapping, so null arguments would never be used.  */
>>> -  auto_vec< vec<tree> > lists (count);
>>> -  lists.quick_grow_cleared (count);
>>> +  auto_vec< vec<tree> > lists (depth);
>>> +  lists.quick_grow_cleared (depth);
>>>      for (tree p = map; p; p = TREE_CHAIN (p))
>>>        {
>>>          int level;
>>> @@ -2862,12 +2868,12 @@ get_mapped_args (tree map)
>>>          /* Insert the argument into its corresponding position.  */
>>>          vec<tree> &list = lists[level - 1];
>>>          if (index >= (int)list.length ())
>>> -	list.safe_grow_cleared (index + 1, true);
>>> +	list.safe_grow_cleared (index + 1, /*exact=*/false);
>>>          list[index] = TREE_PURPOSE (p);
>>>        }
>>>        /* Build the new argument list.  */
>>> -  tree args = make_tree_vec (lists.length ());
>>> +  args = make_tree_vec (lists.length ());
>>>      for (unsigned i = 0; i != lists.length (); ++i)
>>>        {
>>>          vec<tree> &list = lists[i];
>>> @@ -2879,6 +2885,16 @@ get_mapped_args (tree map)
>>>        }
>>>      SET_NON_DEFAULT_TEMPLATE_ARGS_COUNT (args, 0);
>>>    +  if (TMPL_ARGS_HAVE_MULTIPLE_LEVELS (args)
>>> +      && TMPL_ARGS_DEPTH (args) == 1)
>>> +    {
>>> +      /* Micro-optimization: represent a depth-one argument vector
>>> +	 using a single level.  */
>>> +      tree level = TMPL_ARGS_LEVEL (args, 1);
>>> +      ggc_free (args);
>>> +      args = level;
>>> +    }
>>> +
>>>      return args;
>>>    }
>>>    @@ -2933,7 +2949,7 @@ satisfy_atom (tree t, tree args, sat_info info)
>>>        }
>>>        /* Rebuild the argument vector from the parameter mapping.  */
>>> -  args = get_mapped_args (map);
>>> +  args = get_mapped_args (t, args);
>>>        /* Apply the parameter mapping (i.e., just substitute).  */
>>>      tree expr = ATOMIC_CONSTR_EXPR (t);
>>> @@ -2955,7 +2971,7 @@ satisfy_atom (tree t, tree args, sat_info info)
>>>      if (!same_type_p (TREE_TYPE (result), boolean_type_node))
>>>        {
>>>          if (info.noisy ())
>>> -	diagnose_atomic_constraint (t, map, result, info);
>>> +	diagnose_atomic_constraint (t, args, result, info);
>>>          return cache.save (inst_cache.save (error_mark_node));
>>>        }
>>>    @@ -2974,7 +2990,7 @@ satisfy_atom (tree t, tree args, sat_info info)
>>>        }
>>>      result = satisfaction_value (result);
>>>      if (result == boolean_false_node && info.diagnose_unsatisfaction_p ())
>>> -    diagnose_atomic_constraint (t, map, result, info);
>>> +    diagnose_atomic_constraint (t, args, result, info);
>>>        return cache.save (inst_cache.save (result));
>>>    }
>>> @@ -3642,11 +3658,10 @@ diagnose_trait_expr (tree expr, tree args)
>>>        }
>>>    }
>>>    -/* Diagnose a substitution failure in the atomic constraint T when
>>> applied
>>> -   with the instantiated parameter mapping MAP.  */
>>> +/* Diagnose a substitution failure in the atomic constraint T using ARGS.
>>> */
>>>      static void
>>> -diagnose_atomic_constraint (tree t, tree map, tree result, sat_info info)
>>> +diagnose_atomic_constraint (tree t, tree args, tree result, sat_info info)
>>>    {
>>>      /* If the constraint is already ill-formed, we've previously diagnosed
>>>         the reason. We should still say why the constraints aren't satisfied.
>>> */
>>> @@ -3667,7 +3682,6 @@ diagnose_atomic_constraint (tree t, tree map, tree
>>> result, sat_info info)
>>>      /* Generate better diagnostics for certain kinds of expressions.  */
>>>      tree expr = ATOMIC_CONSTR_EXPR (t);
>>>      STRIP_ANY_LOCATION_WRAPPER (expr);
>>> -  tree args = get_mapped_args (map);
>>>      switch (TREE_CODE (expr))
>>>        {
>>>        case TRAIT_EXPR:
>>> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
>>> index f253b32c3f2..dc2429a8406 100644
>>> --- a/gcc/cp/cp-tree.h
>>> +++ b/gcc/cp/cp-tree.h
>>> @@ -466,6 +466,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];
>>>          IMPLICIT_CONV_EXPR_NONTYPE_ARG (in IMPLICIT_CONV_EXPR)
>>>          BASELINK_FUNCTIONS_MAYBE_INCOMPLETE_P (in BASELINK)
>>>          BIND_EXPR_VEC_DTOR (in BIND_EXPR)
>>> +      ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P (in ATOMIC_CONSTR)
>>>       2: IDENTIFIER_KIND_BIT_2 (in IDENTIFIER_NODE)
>>>          ICS_THIS_FLAG (in _CONV)
>>>          DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (in VAR_DECL)
>>> @@ -1679,6 +1680,11 @@ check_constraint_info (tree t)
>>>    #define ATOMIC_CONSTR_MAP_INSTANTIATED_P(NODE) \
>>>      TREE_LANG_FLAG_0 (ATOMIC_CONSTR_CHECK (NODE))
>>>    +/* Whether the expression for this atomic constraint belongs to a
>>> +   concept definition.  */
>>> +#define ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P(NODE) \
>>> +  TREE_LANG_FLAG_1 (ATOMIC_CONSTR_CHECK (NODE))
>>> +
>>>    /* The expression of an atomic constraint. */
>>>    #define ATOMIC_CONSTR_EXPR(NODE) \
>>>      CONSTR_EXPR (ATOMIC_CONSTR_CHECK (NODE))
>>> @@ -8306,7 +8312,6 @@ extern tree evaluate_requires_expr
>>> (tree);
>>>    extern tree tsubst_constraint                   (tree, tree,
>>> tsubst_flags_t, tree);
>>>    extern tree tsubst_constraint_info              (tree, tree,
>>> tsubst_flags_t, tree);
>>>    extern tree tsubst_parameter_mapping		(tree, tree,
>>> tsubst_flags_t, tree);
>>> -extern tree get_mapped_args			(tree);
>>>      struct processing_constraint_expression_sentinel
>>>    {
>>> diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-return-req4.C
>>> b/gcc/testsuite/g++.dg/cpp2a/concepts-return-req4.C
>>> new file mode 100644
>>> index 00000000000..471946bc8eb
>>> --- /dev/null
>>> +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-return-req4.C
>>> @@ -0,0 +1,24 @@
>>> +// PR c++/104527
>>> +// { dg-do compile { target c++20 } }
>>> +
>>> +template<class T, class U>
>>> +concept is_same = __is_same(T, U);
>>> +
>>> +template<class T>
>>> +struct A {
>>> +  template<class...>
>>> +    requires requires { { 0 } -> is_same<T>; }
>>> +  struct B {};
>>> +
>>> +  template<class...>
>>> +    requires requires { { 1 } -> is_same<T>; }
>>> +  static void f();
>>> +};
>>> +
>>> +A<int>::B<> a1;
>>> +A<bool>::B<> a2; // { dg-error "constraint" }
>>> +
>>> +int main() {
>>> +  A<int>::f();
>>> +  A<bool>::f(); // { dg-error "no match" }
>>> +}
>>
>>
>
diff mbox series

Patch

diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 1b18e2a7787..4ff2710b8ba 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -30215,20 +30215,32 @@  do_auto_deduction (tree type, tree init, tree auto_node,
 
       tree full_targs = add_to_template_args (outer_targs, targs);
 
-      /* HACK: Compensate for callers not always communicating all levels of
-	 outer template arguments by filling in the outermost missing levels
-	 with dummy levels before checking satisfaction.  We'll still crash
-	 if the constraint depends on a template argument belonging to one of
-	 these missing levels, but this hack otherwise allows us to handle a
-	 large subset of possible constraints (including all non-dependent
-	 constraints).  */
       if (int missing_levels = (TEMPLATE_TYPE_ORIG_LEVEL (auto_node)
 				- TMPL_ARGS_DEPTH (full_targs)))
 	{
 	  tree dummy_levels = make_tree_vec (missing_levels);
 	  for (int i = 0; i < missing_levels; ++i)
 	    TREE_VEC_ELT (dummy_levels, i) = make_tree_vec (0);
-	  full_targs = add_to_template_args (dummy_levels, full_targs);
+	  if (context == adc_requirement)
+	    /* We're checking a requires-expr's return-type-requirement that's
+	       part of an atomic constraint that doesn't depend on any innermost
+	       template arguments, so OUTER_TARGS (built by get_mapped_args) is
+	       missing at least one innermost level.  Fill in the innermost
+	       levels of OUTER_TARGS with dummy levels.  */
+	    full_targs = add_to_template_args
+	      (add_to_template_args (outer_targs, dummy_levels), targs);
+	  else
+	    {
+	      /* Otherwise, fill in the _outermost_ levels with dummy levels.
+		 This compensates for adc_unify callers that only pass the
+		 innermost level of template arguments as OUTER_TARGS.  We'll
+		 still crash if the constraint depends on a template argument
+		 belonging to one of these missing levels, but this hack
+		 otherwise allows us to handle a large subset of possible
+		 constraints (including all non-dependent constraints).  */
+	      gcc_checking_assert (context == adc_unify);
+	      full_targs = add_to_template_args (dummy_levels, full_targs);
+	    }
 	}
 
       if (!constraints_satisfied_p (auto_node, full_targs))
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-return-req4.C b/gcc/testsuite/g++.dg/cpp2a/concepts-return-req4.C
new file mode 100644
index 00000000000..471946bc8eb
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-return-req4.C
@@ -0,0 +1,24 @@ 
+// PR c++/104527
+// { dg-do compile { target c++20 } }
+
+template<class T, class U>
+concept is_same = __is_same(T, U);
+
+template<class T>
+struct A {
+  template<class...>
+    requires requires { { 0 } -> is_same<T>; }
+  struct B {};
+
+  template<class...>
+    requires requires { { 1 } -> is_same<T>; }
+  static void f();
+};
+
+A<int>::B<> a1;
+A<bool>::B<> a2; // { dg-error "constraint" }
+
+int main() {
+  A<int>::f();
+  A<bool>::f(); // { dg-error "no match" }
+}