public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATCH] c++: adc_unify deduction with constrained auto [PR99365]
@ 2021-03-04 16:26 Patrick Palka
  2021-03-04 16:32 ` Patrick Palka
  0 siblings, 1 reply; 9+ messages in thread
From: Patrick Palka @ 2021-03-04 16:26 UTC (permalink / raw)
  To: gcc-patches

My recent r11-7454 changed the way do_auto_deduction handles constrained
placeholders during template argument deduction (context == adc_unify)
when processing_template_decl != 0.

Before the patch, when processing_template_decl != 0 we would just
ignore the constraints on the placeholder in this situation, and proceed
with deduction:

  /* Check any placeholder constraints against the deduced type. */
  if (flag_concepts && !processing_template_decl)
    if (tree check = NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
      {
        ...

After the patch, we now punt and return the original placeholder type:

  /* Check any placeholder constraints against the deduced type. */
  if (flag_concepts)
    if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
      {
        if (processing_template_decl)
          /* In general we can't check satisfaction until we know all
             template arguments.  */
          return type;
        ...

While this change fixed instances where we'd prematurely resolve a
constrained placeholder return or variable type with non-dependent
initializer at template parse time (such as PR96444), it broke the
adc_unify callers that rely on this previous behavior.

So this patch restores the previous behavior during adc_unify deduction
while retaining the new behavior only during adc_variable_type or
adc_return_type deduction.

We additionally now need to pass outer template arguments to
do_auto_deduction during unify, for sake of constraint checking.
But we don't want do_auto_deduction to substitute these outer arguments
into type if it's already been done, hence the added TEMPLATE_TYPE_LEVEL
check.

This fixes partial specializations of non-nested templates with
constrained 'auto' template parameters, but nested templates are still
broken, ultimately because most_specialized_partial_spec passes only the
innermost template arguments to get_partial_spec_bindings, and so
outer_targs during do_auto_deduction (called from unify) contains only
the innermost template arguments which makes satisfaction unhappy.
Fixing this might be too invasive at this stage, perhaps..  (Seems we
need to make most_specialized_partial_spec pass all template arguments
to get_partial_spec_bindings.)

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

gcc/cp/ChangeLog:

	PR c++/99365
	* pt.c (do_auto_deduction): When processing_template_decl != 0
	and context is adc_unify and we have constraints, pretend the
	constraints are satisfied instead of punting.  Add some
	clarifying sanity checks.  Don't substitute outer_targs into
	type if not needed.

gcc/testsuite/ChangeLog:

	PR c++/99365
	* g++.dg/cpp2a/concepts-partial-spec9.C: New test.
---
 gcc/cp/pt.c                                   | 98 +++++++++++--------
 .../g++.dg/cpp2a/concepts-partial-spec9.C     | 24 +++++
 2 files changed, 79 insertions(+), 43 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C

diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
index a4686e0affb..ce537e4529a 100644
--- a/gcc/cp/pt.c
+++ b/gcc/cp/pt.c
@@ -23693,7 +23693,8 @@ unify (tree tparms, tree targs, tree parm, tree arg, int strict,
 
 	  if (tree a = type_uses_auto (tparm))
 	    {
-	      tparm = do_auto_deduction (tparm, arg, a, complain, adc_unify);
+	      tparm = do_auto_deduction (tparm, arg, a,
+					 complain, adc_unify, targs);
 	      if (tparm == error_mark_node)
 		return 1;
 	    }
@@ -29619,52 +29620,63 @@ do_auto_deduction (tree type, tree init, tree auto_node,
     }
 
   /* Check any placeholder constraints against the deduced type. */
-  if (flag_concepts)
-    if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
-      {
-	if (processing_template_decl)
-	  /* In general we can't check satisfaction until we know all
-	     template arguments.  */
+  if (processing_template_decl && context == adc_unify)
+    /* Pretend constraints are satisfied.  */;
+  else if (flag_concepts
+	   && NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
+    {
+      if (processing_template_decl)
+	{
+	  /* Even though the initializer is non-dependent, we need to wait until
+	     instantiation time to resolve this constrained placeholder variable
+	     or return type, since the constraint itself may be dependent.  */
+	  gcc_checking_assert (context == adc_variable_type
+			       || context == adc_return_type);
+	  gcc_checking_assert (!type_dependent_expression_p (init));
 	  return type;
+	}
 
-	if ((context == adc_return_type || context == adc_variable_type)
-	    && current_function_decl
-	    && DECL_TEMPLATE_INFO (current_function_decl))
-	  outer_targs = DECL_TI_ARGS (current_function_decl);
+      if ((context == adc_return_type || context == adc_variable_type)
+	  && current_function_decl
+	  && DECL_TEMPLATE_INFO (current_function_decl))
+	outer_targs = DECL_TI_ARGS (current_function_decl);
 
-	tree full_targs = add_to_template_args (outer_targs, targs);
-	if (!constraints_satisfied_p (auto_node, full_targs))
-          {
-            if (complain & tf_warning_or_error)
-              {
-		auto_diagnostic_group d;
-                switch (context)
-                  {
-                  case adc_unspecified:
-		  case adc_unify:
-                    error("placeholder constraints not satisfied");
-                    break;
-                  case adc_variable_type:
-		  case adc_decomp_type:
-                    error ("deduced initializer does not satisfy "
-                           "placeholder constraints");
-                    break;
-                  case adc_return_type:
-                    error ("deduced return type does not satisfy "
-                           "placeholder constraints");
-                    break;
-                  case adc_requirement:
-		    error ("deduced expression type does not satisfy "
-                           "placeholder constraints");
-                    break;
-                  }
-		diagnose_constraints (input_location, auto_node, full_targs);
-              }
-            return error_mark_node;
-          }
-      }
+      tree full_targs = add_to_template_args (outer_targs, targs);
+      if (!constraints_satisfied_p (auto_node, full_targs))
+	{
+	  if (complain & tf_warning_or_error)
+	    {
+	      auto_diagnostic_group d;
+	      switch (context)
+		{
+		case adc_unspecified:
+		case adc_unify:
+		  error("placeholder constraints not satisfied");
+		  break;
+		case adc_variable_type:
+		case adc_decomp_type:
+		  error ("deduced initializer does not satisfy "
+			 "placeholder constraints");
+		  break;
+		case adc_return_type:
+		  error ("deduced return type does not satisfy "
+			 "placeholder constraints");
+		  break;
+		case adc_requirement:
+		  error ("deduced expression type does not satisfy "
+			 "placeholder constraints");
+		  break;
+		}
+	      diagnose_constraints (input_location, auto_node, full_targs);
+	    }
+	  return error_mark_node;
+	}
+    }
 
-  if (context == adc_unify)
+  if (TEMPLATE_TYPE_LEVEL (auto_node) == 1)
+    /* The outer template arguments are already substituted into type
+       (but we still may have used them for constraint checking above).  */;
+  else if (context == adc_unify)
     targs = add_to_template_args (outer_targs, targs);
   else if (processing_template_decl)
     targs = add_to_template_args (current_template_args (), targs);
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C b/gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
new file mode 100644
index 00000000000..b79d12b6f17
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
@@ -0,0 +1,24 @@
+// PR c++/99365
+// { dg-do compile { target c++20 } }
+
+template <class> concept C = true;
+template <class T, class U> concept D = C<T> && __is_same(T, U);
+
+template <class, C auto> struct A { static const int i = 0; };
+template <class T, D<T> auto V> struct A<T, V> { static const int i = 1; };
+
+static_assert(A<int, 0>::i == 1);
+static_assert(A<char, 0>::i == 0);
+static_assert(A<int, '0'>::i == 0);
+static_assert(A<char, '0'>::i == 1);
+
+/* Partial specialization of nested class template with constrained 'auto'
+   template parameter still broken:
+
+template <class> struct O {
+  template <C auto> struct B { static const int i = 0; };
+  template <D auto V> struct B<V> { static const int i = 1; };
+};
+
+static_assert(O<void>::B<int, 0>::i == 0);
+static_assert(O<void>::B<int, '0'>::i == 1);  */
-- 
2.31.0.rc0.75.gec125d1bc1


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH] c++: adc_unify deduction with constrained auto [PR99365]
  2021-03-04 16:26 [PATCH] c++: adc_unify deduction with constrained auto [PR99365] Patrick Palka
@ 2021-03-04 16:32 ` Patrick Palka
  2021-03-04 21:03   ` Jason Merrill
  0 siblings, 1 reply; 9+ messages in thread
From: Patrick Palka @ 2021-03-04 16:32 UTC (permalink / raw)
  To: Patrick Palka; +Cc: gcc-patches, jason

On Thu, 4 Mar 2021, Patrick Palka wrote:

> My recent r11-7454 changed the way do_auto_deduction handles constrained
> placeholders during template argument deduction (context == adc_unify)
> when processing_template_decl != 0.
> 
> Before the patch, when processing_template_decl != 0 we would just
> ignore the constraints on the placeholder in this situation, and proceed
> with deduction:
> 
>   /* Check any placeholder constraints against the deduced type. */
>   if (flag_concepts && !processing_template_decl)
>     if (tree check = NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
>       {
>         ...
> 
> After the patch, we now punt and return the original placeholder type:
> 
>   /* Check any placeholder constraints against the deduced type. */
>   if (flag_concepts)
>     if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
>       {
>         if (processing_template_decl)
>           /* In general we can't check satisfaction until we know all
>              template arguments.  */
>           return type;
>         ...
> 
> While this change fixed instances where we'd prematurely resolve a
> constrained placeholder return or variable type with non-dependent
> initializer at template parse time (such as PR96444), it broke the
> adc_unify callers that rely on this previous behavior.
> 
> So this patch restores the previous behavior during adc_unify deduction
> while retaining the new behavior only during adc_variable_type or
> adc_return_type deduction.
> 
> We additionally now need to pass outer template arguments to
> do_auto_deduction during unify, for sake of constraint checking.
> But we don't want do_auto_deduction to substitute these outer arguments
> into type if it's already been done, hence the added TEMPLATE_TYPE_LEVEL
> check.
> 
> This fixes partial specializations of non-nested templates with
> constrained 'auto' template parameters, but nested templates are still
> broken, ultimately because most_specialized_partial_spec passes only the
> innermost template arguments to get_partial_spec_bindings, and so
> outer_targs during do_auto_deduction (called from unify) contains only
> the innermost template arguments which makes satisfaction unhappy.
> Fixing this might be too invasive at this stage, perhaps..  (Seems we
> need to make most_specialized_partial_spec pass all template arguments
> to get_partial_spec_bindings.)
> 
> Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for
> trunk?  Also tested on range-v3 and cmcstl2.

Here's the same patch generated with -w which hides the noisy indentation
changes:

-- >8 --

	PR c++/99365
	* pt.c (do_auto_deduction): When processing_template_decl != 0
	and context is adc_unify and we have constraints, pretend the
	constraints are satisfied instead of punting.  Add some
	clarifying sanity checks.  Don't substitute outer_targs into
	type if not needed.

gcc/testsuite/ChangeLog:

	PR c++/99365
	* g++.dg/cpp2a/concepts-partial-spec9.C: New test.
---
 gcc/cp/pt.c                                   | 24 ++++++++++++++-----
 .../g++.dg/cpp2a/concepts-partial-spec9.C     | 24 +++++++++++++++++++
 2 files changed, 42 insertions(+), 6 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C

diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
index a4686e0affb..ce537e4529a 100644
--- a/gcc/cp/pt.c
+++ b/gcc/cp/pt.c
@@ -23693,7 +23693,8 @@ unify (tree tparms, tree targs, tree parm, tree arg, int strict,
 
 	  if (tree a = type_uses_auto (tparm))
 	    {
-	      tparm = do_auto_deduction (tparm, arg, a, complain, adc_unify);
+	      tparm = do_auto_deduction (tparm, arg, a,
+					 complain, adc_unify, targs);
 	      if (tparm == error_mark_node)
 		return 1;
 	    }
@@ -29619,13 +29620,21 @@ do_auto_deduction (tree type, tree init, tree auto_node,
     }
 
   /* Check any placeholder constraints against the deduced type. */
-  if (flag_concepts)
-    if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
+  if (processing_template_decl && context == adc_unify)
+    /* Pretend constraints are satisfied.  */;
+  else if (flag_concepts
+	   && NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
     {
       if (processing_template_decl)
-	  /* In general we can't check satisfaction until we know all
-	     template arguments.  */
+	{
+	  /* Even though the initializer is non-dependent, we need to wait until
+	     instantiation time to resolve this constrained placeholder variable
+	     or return type, since the constraint itself may be dependent.  */
+	  gcc_checking_assert (context == adc_variable_type
+			       || context == adc_return_type);
+	  gcc_checking_assert (!type_dependent_expression_p (init));
 	  return type;
+	}
 
       if ((context == adc_return_type || context == adc_variable_type)
 	  && current_function_decl
@@ -29664,7 +29673,10 @@ do_auto_deduction (tree type, tree init, tree auto_node,
 	}
     }
 
-  if (context == adc_unify)
+  if (TEMPLATE_TYPE_LEVEL (auto_node) == 1)
+    /* The outer template arguments are already substituted into type
+       (but we still may have used them for constraint checking above).  */;
+  else if (context == adc_unify)
     targs = add_to_template_args (outer_targs, targs);
   else if (processing_template_decl)
     targs = add_to_template_args (current_template_args (), targs);
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C b/gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
new file mode 100644
index 00000000000..b79d12b6f17
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
@@ -0,0 +1,24 @@
+// PR c++/99365
+// { dg-do compile { target c++20 } }
+
+template <class> concept C = true;
+template <class T, class U> concept D = C<T> && __is_same(T, U);
+
+template <class, C auto> struct A { static const int i = 0; };
+template <class T, D<T> auto V> struct A<T, V> { static const int i = 1; };
+
+static_assert(A<int, 0>::i == 1);
+static_assert(A<char, 0>::i == 0);
+static_assert(A<int, '0'>::i == 0);
+static_assert(A<char, '0'>::i == 1);
+
+/* Partial specialization of nested class template with constrained 'auto'
+   template parameter still broken:
+
+template <class> struct O {
+  template <C auto> struct B { static const int i = 0; };
+  template <D auto V> struct B<V> { static const int i = 1; };
+};
+
+static_assert(O<void>::B<int, 0>::i == 0);
+static_assert(O<void>::B<int, '0'>::i == 1);  */
-- 
2.31.0.rc0.75.gec125d1bc1


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH] c++: adc_unify deduction with constrained auto [PR99365]
  2021-03-04 16:32 ` Patrick Palka
@ 2021-03-04 21:03   ` Jason Merrill
  2021-03-04 21:45     ` Patrick Palka
  0 siblings, 1 reply; 9+ messages in thread
From: Jason Merrill @ 2021-03-04 21:03 UTC (permalink / raw)
  To: Patrick Palka; +Cc: gcc-patches

On 3/4/21 11:32 AM, Patrick Palka wrote:
> On Thu, 4 Mar 2021, Patrick Palka wrote:
> 
>> My recent r11-7454 changed the way do_auto_deduction handles constrained
>> placeholders during template argument deduction (context == adc_unify)
>> when processing_template_decl != 0.
>>
>> Before the patch, when processing_template_decl != 0 we would just
>> ignore the constraints on the placeholder in this situation, and proceed
>> with deduction:
>>
>>    /* Check any placeholder constraints against the deduced type. */
>>    if (flag_concepts && !processing_template_decl)
>>      if (tree check = NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
>>        {
>>          ...
>>
>> After the patch, we now punt and return the original placeholder type:
>>
>>    /* Check any placeholder constraints against the deduced type. */
>>    if (flag_concepts)
>>      if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
>>        {
>>          if (processing_template_decl)
>>            /* In general we can't check satisfaction until we know all
>>               template arguments.  */
>>            return type;
>>          ...
>>
>> While this change fixed instances where we'd prematurely resolve a
>> constrained placeholder return or variable type with non-dependent
>> initializer at template parse time (such as PR96444), it broke the
>> adc_unify callers that rely on this previous behavior.
>>
>> So this patch restores the previous behavior during adc_unify deduction
>> while retaining the new behavior only during adc_variable_type or
>> adc_return_type deduction.

Sure, it makes sense for adc_unify to behave differently, since in 
deduction context constraints are checked after all template args have 
been deduced.

>> We additionally now need to pass outer template arguments to
>> do_auto_deduction during unify, for sake of constraint checking.
>> But we don't want do_auto_deduction to substitute these outer arguments
>> into type if it's already been done, hence the added TEMPLATE_TYPE_LEVEL
>> check.
>>
>> This fixes partial specializations of non-nested templates with
>> constrained 'auto' template parameters, but nested templates are still
>> broken, ultimately because most_specialized_partial_spec passes only the
>> innermost template arguments to get_partial_spec_bindings, and so
>> outer_targs during do_auto_deduction (called from unify) contains only
>> the innermost template arguments which makes satisfaction unhappy.
>> Fixing this might be too invasive at this stage, perhaps..  (Seems we
>> need to make most_specialized_partial_spec pass all template arguments
>> to get_partial_spec_bindings.)

How did this work before?

>> Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for
>> trunk?  Also tested on range-v3 and cmcstl2.
> 
> Here's the same patch generated with -w which hides the noisy indentation
> changes:
> 
> -- >8 --
> 
> 	PR c++/99365
> 	* pt.c (do_auto_deduction): When processing_template_decl != 0
> 	and context is adc_unify and we have constraints, pretend the
> 	constraints are satisfied instead of punting.  Add some
> 	clarifying sanity checks.  Don't substitute outer_targs into
> 	type if not needed.
> 
> gcc/testsuite/ChangeLog:
> 
> 	PR c++/99365
> 	* g++.dg/cpp2a/concepts-partial-spec9.C: New test.
> ---
>   gcc/cp/pt.c                                   | 24 ++++++++++++++-----
>   .../g++.dg/cpp2a/concepts-partial-spec9.C     | 24 +++++++++++++++++++
>   2 files changed, 42 insertions(+), 6 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
> 
> diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
> index a4686e0affb..ce537e4529a 100644
> --- a/gcc/cp/pt.c
> +++ b/gcc/cp/pt.c
> @@ -23693,7 +23693,8 @@ unify (tree tparms, tree targs, tree parm, tree arg, int strict,
>   
>   	  if (tree a = type_uses_auto (tparm))
>   	    {
> -	      tparm = do_auto_deduction (tparm, arg, a, complain, adc_unify);
> +	      tparm = do_auto_deduction (tparm, arg, a,
> +					 complain, adc_unify, targs);
>   	      if (tparm == error_mark_node)
>   		return 1;
>   	    }
> @@ -29619,13 +29620,21 @@ do_auto_deduction (tree type, tree init, tree auto_node,
>       }
>   
>     /* Check any placeholder constraints against the deduced type. */
> -  if (flag_concepts)
> -    if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
> +  if (processing_template_decl && context == adc_unify)
> +    /* Pretend constraints are satisfied.  */;
> +  else if (flag_concepts
> +	   && NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
>       {
>         if (processing_template_decl)
> -	  /* In general we can't check satisfaction until we know all
> -	     template arguments.  */
> +	{
> +	  /* Even though the initializer is non-dependent, we need to wait until
> +	     instantiation time to resolve this constrained placeholder variable
> +	     or return type, since the constraint itself may be dependent.  */

Can't we check whether the constraint is dependent, and check 
satisfaction if it isn't?  That might be necessary to make later 
expressions non-dependent that are supposed to be.

> +	  gcc_checking_assert (context == adc_variable_type
> +			       || context == adc_return_type);
> +	  gcc_checking_assert (!type_dependent_expression_p (init));
>   	  return type;
> +	}
>   
>         if ((context == adc_return_type || context == adc_variable_type)
>   	  && current_function_decl
> @@ -29664,7 +29673,10 @@ do_auto_deduction (tree type, tree init, tree auto_node,
>   	}
>       }
>   
> -  if (context == adc_unify)
> +  if (TEMPLATE_TYPE_LEVEL (auto_node) == 1)
> +    /* The outer template arguments are already substituted into type
> +       (but we still may have used them for constraint checking above).  */;
> +  else if (context == adc_unify)
>       targs = add_to_template_args (outer_targs, targs);
>     else if (processing_template_decl)
>       targs = add_to_template_args (current_template_args (), targs);
> diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C b/gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
> new file mode 100644
> index 00000000000..b79d12b6f17
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
> @@ -0,0 +1,24 @@
> +// PR c++/99365
> +// { dg-do compile { target c++20 } }
> +
> +template <class> concept C = true;
> +template <class T, class U> concept D = C<T> && __is_same(T, U);
> +
> +template <class, C auto> struct A { static const int i = 0; };
> +template <class T, D<T> auto V> struct A<T, V> { static const int i = 1; };
> +
> +static_assert(A<int, 0>::i == 1);
> +static_assert(A<char, 0>::i == 0);
> +static_assert(A<int, '0'>::i == 0);
> +static_assert(A<char, '0'>::i == 1);
> +
> +/* Partial specialization of nested class template with constrained 'auto'
> +   template parameter still broken:
> +
> +template <class> struct O {
> +  template <C auto> struct B { static const int i = 0; };
> +  template <D auto V> struct B<V> { static const int i = 1; };
> +};
> +
> +static_assert(O<void>::B<int, 0>::i == 0);
> +static_assert(O<void>::B<int, '0'>::i == 1);  */
> 


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH] c++: adc_unify deduction with constrained auto [PR99365]
  2021-03-04 21:03   ` Jason Merrill
@ 2021-03-04 21:45     ` Patrick Palka
  2021-03-04 21:51       ` Patrick Palka
  0 siblings, 1 reply; 9+ messages in thread
From: Patrick Palka @ 2021-03-04 21:45 UTC (permalink / raw)
  To: Jason Merrill; +Cc: Patrick Palka, gcc-patches

On Thu, 4 Mar 2021, Jason Merrill wrote:

> On 3/4/21 11:32 AM, Patrick Palka wrote:
> > On Thu, 4 Mar 2021, Patrick Palka wrote:
> > 
> > > My recent r11-7454 changed the way do_auto_deduction handles constrained
> > > placeholders during template argument deduction (context == adc_unify)
> > > when processing_template_decl != 0.
> > > 
> > > Before the patch, when processing_template_decl != 0 we would just
> > > ignore the constraints on the placeholder in this situation, and proceed
> > > with deduction:
> > > 
> > >    /* Check any placeholder constraints against the deduced type. */
> > >    if (flag_concepts && !processing_template_decl)
> > >      if (tree check = NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS
> > > (auto_node)))
> > >        {
> > >          ...
> > > 
> > > After the patch, we now punt and return the original placeholder type:
> > > 
> > >    /* Check any placeholder constraints against the deduced type. */
> > >    if (flag_concepts)
> > >      if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
> > >        {
> > >          if (processing_template_decl)
> > >            /* In general we can't check satisfaction until we know all
> > >               template arguments.  */
> > >            return type;
> > >          ...
> > > 
> > > While this change fixed instances where we'd prematurely resolve a
> > > constrained placeholder return or variable type with non-dependent
> > > initializer at template parse time (such as PR96444), it broke the
> > > adc_unify callers that rely on this previous behavior.
> > > 
> > > So this patch restores the previous behavior during adc_unify deduction
> > > while retaining the new behavior only during adc_variable_type or
> > > adc_return_type deduction.
> 
> Sure, it makes sense for adc_unify to behave differently, since in deduction
> context constraints are checked after all template args have been deduced.

I see.

> 
> > > We additionally now need to pass outer template arguments to
> > > do_auto_deduction during unify, for sake of constraint checking.
> > > But we don't want do_auto_deduction to substitute these outer arguments
> > > into type if it's already been done, hence the added TEMPLATE_TYPE_LEVEL
> > > check.
> > > 
> > > This fixes partial specializations of non-nested templates with
> > > constrained 'auto' template parameters, but nested templates are still
> > > broken, ultimately because most_specialized_partial_spec passes only the
> > > innermost template arguments to get_partial_spec_bindings, and so
> > > outer_targs during do_auto_deduction (called from unify) contains only
> > > the innermost template arguments which makes satisfaction unhappy.
> > > Fixing this might be too invasive at this stage, perhaps..  (Seems we
> > > need to make most_specialized_partial_spec pass all template arguments
> > > to get_partial_spec_bindings.)
> 
> How did this work before?

Before, it would work, but only if the constraint didn't also depend on
any outer template arguments.  do_auto_deduction would just "surgically"
replace the auto in the concept-id with the type we deduced and leave
the other template arguments of the concept-id alone.  So if the
constraint was non-dependent, satisfaction would work regardless of the
template nesting level.

Now that we try to do perform satisfaction properly, we're sensitive to
the template nesting level even if the constraint is otherwise
non-dependent, because the template nesting level determines the level
of the auto that appears inside the constraint.  So we rely on
outer_targs to contain all levels of outer template arguments, because
we tack on another level to the end of outer_targs which needs to
map to the level of the auto for satisfaction.

(As a hacky workaround, when outer_targs is incomplete, can probably
just augment it with empty levels until it's TEMPLATE_TYPE_LEVEL(auto_node)-1
levels deep, which would fix the nested template case as long as the
constraint was depended only on the innermost level of template
arguments.)

> 
> > > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for
> > > trunk?  Also tested on range-v3 and cmcstl2.
> > 
> > Here's the same patch generated with -w which hides the noisy indentation
> > changes:
> > 
> > -- >8 --
> > 
> > 	PR c++/99365
> > 	* pt.c (do_auto_deduction): When processing_template_decl != 0
> > 	and context is adc_unify and we have constraints, pretend the
> > 	constraints are satisfied instead of punting.  Add some
> > 	clarifying sanity checks.  Don't substitute outer_targs into
> > 	type if not needed.
> > 
> > gcc/testsuite/ChangeLog:
> > 
> > 	PR c++/99365
> > 	* g++.dg/cpp2a/concepts-partial-spec9.C: New test.
> > ---
> >   gcc/cp/pt.c                                   | 24 ++++++++++++++-----
> >   .../g++.dg/cpp2a/concepts-partial-spec9.C     | 24 +++++++++++++++++++
> >   2 files changed, 42 insertions(+), 6 deletions(-)
> >   create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
> > 
> > diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
> > index a4686e0affb..ce537e4529a 100644
> > --- a/gcc/cp/pt.c
> > +++ b/gcc/cp/pt.c
> > @@ -23693,7 +23693,8 @@ unify (tree tparms, tree targs, tree parm, tree arg,
> > int strict,
> >     	  if (tree a = type_uses_auto (tparm))
> >   	    {
> > -	      tparm = do_auto_deduction (tparm, arg, a, complain, adc_unify);
> > +	      tparm = do_auto_deduction (tparm, arg, a,
> > +					 complain, adc_unify, targs);
> >   	      if (tparm == error_mark_node)
> >   		return 1;
> >   	    }
> > @@ -29619,13 +29620,21 @@ do_auto_deduction (tree type, tree init, tree
> > auto_node,
> >       }
> >       /* Check any placeholder constraints against the deduced type. */
> > -  if (flag_concepts)
> > -    if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
> > +  if (processing_template_decl && context == adc_unify)
> > +    /* Pretend constraints are satisfied.  */;
> > +  else if (flag_concepts
> > +	   && NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
> >       {
> >         if (processing_template_decl)
> > -	  /* In general we can't check satisfaction until we know all
> > -	     template arguments.  */
> > +	{
> > +	  /* Even though the initializer is non-dependent, we need to wait
> > until
> > +	     instantiation time to resolve this constrained placeholder
> > variable
> > +	     or return type, since the constraint itself may be dependent.  */
> 
> Can't we check whether the constraint is dependent, and check satisfaction if
> it isn't?  That might be necessary to make later expressions non-dependent
> that are supposed to be.

We'd have to check if outer_targs (and the deduced type) is dependent
too.  But it seems tricky because outer_targs, during adc_unify
deduction, is usually at least partially empty which is enough to make
any_dependent_template_arguments_p return true.

> 
> > +	  gcc_checking_assert (context == adc_variable_type
> > +			       || context == adc_return_type);
> > +	  gcc_checking_assert (!type_dependent_expression_p (init));
> >   	  return type;
> > +	}
> >           if ((context == adc_return_type || context == adc_variable_type)
> >   	  && current_function_decl
> > @@ -29664,7 +29673,10 @@ do_auto_deduction (tree type, tree init, tree
> > auto_node,
> >   	}
> >       }
> >   -  if (context == adc_unify)
> > +  if (TEMPLATE_TYPE_LEVEL (auto_node) == 1)
> > +    /* The outer template arguments are already substituted into type
> > +       (but we still may have used them for constraint checking above).
> > */;
> > +  else if (context == adc_unify)
> >       targs = add_to_template_args (outer_targs, targs);
> >     else if (processing_template_decl)
> >       targs = add_to_template_args (current_template_args (), targs);
> > diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
> > b/gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
> > new file mode 100644
> > index 00000000000..b79d12b6f17
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
> > @@ -0,0 +1,24 @@
> > +// PR c++/99365
> > +// { dg-do compile { target c++20 } }
> > +
> > +template <class> concept C = true;
> > +template <class T, class U> concept D = C<T> && __is_same(T, U);
> > +
> > +template <class, C auto> struct A { static const int i = 0; };
> > +template <class T, D<T> auto V> struct A<T, V> { static const int i = 1; };
> > +
> > +static_assert(A<int, 0>::i == 1);
> > +static_assert(A<char, 0>::i == 0);
> > +static_assert(A<int, '0'>::i == 0);
> > +static_assert(A<char, '0'>::i == 1);
> > +
> > +/* Partial specialization of nested class template with constrained 'auto'
> > +   template parameter still broken:
> > +
> > +template <class> struct O {
> > +  template <C auto> struct B { static const int i = 0; };
> > +  template <D auto V> struct B<V> { static const int i = 1; };
> > +};
> > +
> > +static_assert(O<void>::B<int, 0>::i == 0);
> > +static_assert(O<void>::B<int, '0'>::i == 1);  */
> > 
> 
> 


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH] c++: adc_unify deduction with constrained auto [PR99365]
  2021-03-04 21:45     ` Patrick Palka
@ 2021-03-04 21:51       ` Patrick Palka
  2021-03-05  2:55         ` Patrick Palka
  0 siblings, 1 reply; 9+ messages in thread
From: Patrick Palka @ 2021-03-04 21:51 UTC (permalink / raw)
  To: Patrick Palka; +Cc: Jason Merrill, gcc-patches

On Thu, 4 Mar 2021, Patrick Palka wrote:

> On Thu, 4 Mar 2021, Jason Merrill wrote:
> 
> > On 3/4/21 11:32 AM, Patrick Palka wrote:
> > > On Thu, 4 Mar 2021, Patrick Palka wrote:
> > > 
> > > > My recent r11-7454 changed the way do_auto_deduction handles constrained
> > > > placeholders during template argument deduction (context == adc_unify)
> > > > when processing_template_decl != 0.
> > > > 
> > > > Before the patch, when processing_template_decl != 0 we would just
> > > > ignore the constraints on the placeholder in this situation, and proceed
> > > > with deduction:
> > > > 
> > > >    /* Check any placeholder constraints against the deduced type. */
> > > >    if (flag_concepts && !processing_template_decl)
> > > >      if (tree check = NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS
> > > > (auto_node)))
> > > >        {
> > > >          ...
> > > > 
> > > > After the patch, we now punt and return the original placeholder type:
> > > > 
> > > >    /* Check any placeholder constraints against the deduced type. */
> > > >    if (flag_concepts)
> > > >      if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
> > > >        {
> > > >          if (processing_template_decl)
> > > >            /* In general we can't check satisfaction until we know all
> > > >               template arguments.  */
> > > >            return type;
> > > >          ...
> > > > 
> > > > While this change fixed instances where we'd prematurely resolve a
> > > > constrained placeholder return or variable type with non-dependent
> > > > initializer at template parse time (such as PR96444), it broke the
> > > > adc_unify callers that rely on this previous behavior.
> > > > 
> > > > So this patch restores the previous behavior during adc_unify deduction
> > > > while retaining the new behavior only during adc_variable_type or
> > > > adc_return_type deduction.
> > 
> > Sure, it makes sense for adc_unify to behave differently, since in deduction
> > context constraints are checked after all template args have been deduced.
> 
> I see.
> 
> > 
> > > > We additionally now need to pass outer template arguments to
> > > > do_auto_deduction during unify, for sake of constraint checking.
> > > > But we don't want do_auto_deduction to substitute these outer arguments
> > > > into type if it's already been done, hence the added TEMPLATE_TYPE_LEVEL
> > > > check.
> > > > 
> > > > This fixes partial specializations of non-nested templates with
> > > > constrained 'auto' template parameters, but nested templates are still
> > > > broken, ultimately because most_specialized_partial_spec passes only the
> > > > innermost template arguments to get_partial_spec_bindings, and so
> > > > outer_targs during do_auto_deduction (called from unify) contains only
> > > > the innermost template arguments which makes satisfaction unhappy.
> > > > Fixing this might be too invasive at this stage, perhaps..  (Seems we
> > > > need to make most_specialized_partial_spec pass all template arguments
> > > > to get_partial_spec_bindings.)
> > 
> > How did this work before?
> 
> Before, it would work, but only if the constraint didn't also depend on
> any outer template arguments.  do_auto_deduction would just "surgically"
> replace the auto in the concept-id with the type we deduced and leave
> the other template arguments of the concept-id alone.  So if the
> constraint was non-dependent, satisfaction would work regardless of the
> template nesting level.
> 
> Now that we try to do perform satisfaction properly, we're sensitive to
> the template nesting level even if the constraint is otherwise
> non-dependent, because the template nesting level determines the level
> of the auto that appears inside the constraint.  So we rely on
> outer_targs to contain all levels of outer template arguments, because
> we tack on another level to the end of outer_targs which needs to
> map to the level of the auto for satisfaction.
> 
> (As a hacky workaround, when outer_targs is incomplete, can probably
> just augment it with empty levels until it's TEMPLATE_TYPE_LEVEL(auto_node)-1
> levels deep, which would fix the nested template case as long as the
> constraint was depended only on the innermost level of template
> arguments.)
> 
> > 
> > > > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for
> > > > trunk?  Also tested on range-v3 and cmcstl2.
> > > 
> > > Here's the same patch generated with -w which hides the noisy indentation
> > > changes:
> > > 
> > > -- >8 --
> > > 
> > > 	PR c++/99365
> > > 	* pt.c (do_auto_deduction): When processing_template_decl != 0
> > > 	and context is adc_unify and we have constraints, pretend the
> > > 	constraints are satisfied instead of punting.  Add some
> > > 	clarifying sanity checks.  Don't substitute outer_targs into
> > > 	type if not needed.
> > > 
> > > gcc/testsuite/ChangeLog:
> > > 
> > > 	PR c++/99365
> > > 	* g++.dg/cpp2a/concepts-partial-spec9.C: New test.
> > > ---
> > >   gcc/cp/pt.c                                   | 24 ++++++++++++++-----
> > >   .../g++.dg/cpp2a/concepts-partial-spec9.C     | 24 +++++++++++++++++++
> > >   2 files changed, 42 insertions(+), 6 deletions(-)
> > >   create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
> > > 
> > > diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
> > > index a4686e0affb..ce537e4529a 100644
> > > --- a/gcc/cp/pt.c
> > > +++ b/gcc/cp/pt.c
> > > @@ -23693,7 +23693,8 @@ unify (tree tparms, tree targs, tree parm, tree arg,
> > > int strict,
> > >     	  if (tree a = type_uses_auto (tparm))
> > >   	    {
> > > -	      tparm = do_auto_deduction (tparm, arg, a, complain, adc_unify);
> > > +	      tparm = do_auto_deduction (tparm, arg, a,
> > > +					 complain, adc_unify, targs);
> > >   	      if (tparm == error_mark_node)
> > >   		return 1;
> > >   	    }
> > > @@ -29619,13 +29620,21 @@ do_auto_deduction (tree type, tree init, tree
> > > auto_node,
> > >       }
> > >       /* Check any placeholder constraints against the deduced type. */
> > > -  if (flag_concepts)
> > > -    if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
> > > +  if (processing_template_decl && context == adc_unify)
> > > +    /* Pretend constraints are satisfied.  */;
> > > +  else if (flag_concepts
> > > +	   && NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
> > >       {
> > >         if (processing_template_decl)
> > > -	  /* In general we can't check satisfaction until we know all
> > > -	     template arguments.  */
> > > +	{
> > > +	  /* Even though the initializer is non-dependent, we need to wait
> > > until
> > > +	     instantiation time to resolve this constrained placeholder
> > > variable
> > > +	     or return type, since the constraint itself may be dependent.  */
> > 
> > Can't we check whether the constraint is dependent, and check satisfaction if
> > it isn't?  That might be necessary to make later expressions non-dependent
> > that are supposed to be.
> 
> We'd have to check if outer_targs (and the deduced type) is dependent
> too.  But it seems tricky because outer_targs, during adc_unify
> deduction, is usually at least partially empty which is enough to make
> any_dependent_template_arguments_p return true.

Ah sorry, I just realized you probably meant we should check whether the
constraint is dependent during adc_variable_type and adc_return_type
deduction.  I think that might be straightforward; I'll try it.  I'll
also experiment with the hacky workaround mentioned earlier.

> 
> > 
> > > +	  gcc_checking_assert (context == adc_variable_type
> > > +			       || context == adc_return_type);
> > > +	  gcc_checking_assert (!type_dependent_expression_p (init));
> > >   	  return type;
> > > +	}
> > >           if ((context == adc_return_type || context == adc_variable_type)
> > >   	  && current_function_decl
> > > @@ -29664,7 +29673,10 @@ do_auto_deduction (tree type, tree init, tree
> > > auto_node,
> > >   	}
> > >       }
> > >   -  if (context == adc_unify)
> > > +  if (TEMPLATE_TYPE_LEVEL (auto_node) == 1)
> > > +    /* The outer template arguments are already substituted into type
> > > +       (but we still may have used them for constraint checking above).
> > > */;
> > > +  else if (context == adc_unify)
> > >       targs = add_to_template_args (outer_targs, targs);
> > >     else if (processing_template_decl)
> > >       targs = add_to_template_args (current_template_args (), targs);
> > > diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
> > > b/gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
> > > new file mode 100644
> > > index 00000000000..b79d12b6f17
> > > --- /dev/null
> > > +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
> > > @@ -0,0 +1,24 @@
> > > +// PR c++/99365
> > > +// { dg-do compile { target c++20 } }
> > > +
> > > +template <class> concept C = true;
> > > +template <class T, class U> concept D = C<T> && __is_same(T, U);
> > > +
> > > +template <class, C auto> struct A { static const int i = 0; };
> > > +template <class T, D<T> auto V> struct A<T, V> { static const int i = 1; };
> > > +
> > > +static_assert(A<int, 0>::i == 1);
> > > +static_assert(A<char, 0>::i == 0);
> > > +static_assert(A<int, '0'>::i == 0);
> > > +static_assert(A<char, '0'>::i == 1);
> > > +
> > > +/* Partial specialization of nested class template with constrained 'auto'
> > > +   template parameter still broken:
> > > +
> > > +template <class> struct O {
> > > +  template <C auto> struct B { static const int i = 0; };
> > > +  template <D auto V> struct B<V> { static const int i = 1; };
> > > +};
> > > +
> > > +static_assert(O<void>::B<int, 0>::i == 0);
> > > +static_assert(O<void>::B<int, '0'>::i == 1);  */
> > > 
> > 
> > 
> 


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH] c++: adc_unify deduction with constrained auto [PR99365]
  2021-03-04 21:51       ` Patrick Palka
@ 2021-03-05  2:55         ` Patrick Palka
  2021-03-05 19:12           ` Jason Merrill
  0 siblings, 1 reply; 9+ messages in thread
From: Patrick Palka @ 2021-03-05  2:55 UTC (permalink / raw)
  To: Patrick Palka; +Cc: Jason Merrill, gcc-patches

On Thu, 4 Mar 2021, Patrick Palka wrote:

> On Thu, 4 Mar 2021, Patrick Palka wrote:
> 
> > On Thu, 4 Mar 2021, Jason Merrill wrote:
> > 
> > > On 3/4/21 11:32 AM, Patrick Palka wrote:
> > > > On Thu, 4 Mar 2021, Patrick Palka wrote:
> > > > 
> > > > > My recent r11-7454 changed the way do_auto_deduction handles constrained
> > > > > placeholders during template argument deduction (context == adc_unify)
> > > > > when processing_template_decl != 0.
> > > > > 
> > > > > Before the patch, when processing_template_decl != 0 we would just
> > > > > ignore the constraints on the placeholder in this situation, and proceed
> > > > > with deduction:
> > > > > 
> > > > >    /* Check any placeholder constraints against the deduced type. */
> > > > >    if (flag_concepts && !processing_template_decl)
> > > > >      if (tree check = NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS
> > > > > (auto_node)))
> > > > >        {
> > > > >          ...
> > > > > 
> > > > > After the patch, we now punt and return the original placeholder type:
> > > > > 
> > > > >    /* Check any placeholder constraints against the deduced type. */
> > > > >    if (flag_concepts)
> > > > >      if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
> > > > >        {
> > > > >          if (processing_template_decl)
> > > > >            /* In general we can't check satisfaction until we know all
> > > > >               template arguments.  */
> > > > >            return type;
> > > > >          ...
> > > > > 
> > > > > While this change fixed instances where we'd prematurely resolve a
> > > > > constrained placeholder return or variable type with non-dependent
> > > > > initializer at template parse time (such as PR96444), it broke the
> > > > > adc_unify callers that rely on this previous behavior.
> > > > > 
> > > > > So this patch restores the previous behavior during adc_unify deduction
> > > > > while retaining the new behavior only during adc_variable_type or
> > > > > adc_return_type deduction.
> > > 
> > > Sure, it makes sense for adc_unify to behave differently, since in deduction
> > > context constraints are checked after all template args have been deduced.
> > 
> > I see.
> > 
> > > 
> > > > > We additionally now need to pass outer template arguments to
> > > > > do_auto_deduction during unify, for sake of constraint checking.
> > > > > But we don't want do_auto_deduction to substitute these outer arguments
> > > > > into type if it's already been done, hence the added TEMPLATE_TYPE_LEVEL
> > > > > check.
> > > > > 
> > > > > This fixes partial specializations of non-nested templates with
> > > > > constrained 'auto' template parameters, but nested templates are still
> > > > > broken, ultimately because most_specialized_partial_spec passes only the
> > > > > innermost template arguments to get_partial_spec_bindings, and so
> > > > > outer_targs during do_auto_deduction (called from unify) contains only
> > > > > the innermost template arguments which makes satisfaction unhappy.
> > > > > Fixing this might be too invasive at this stage, perhaps..  (Seems we
> > > > > need to make most_specialized_partial_spec pass all template arguments
> > > > > to get_partial_spec_bindings.)
> > > 
> > > How did this work before?
> > 
> > Before, it would work, but only if the constraint didn't also depend on
> > any outer template arguments.  do_auto_deduction would just "surgically"
> > replace the auto in the concept-id with the type we deduced and leave
> > the other template arguments of the concept-id alone.  So if the
> > constraint was non-dependent, satisfaction would work regardless of the
> > template nesting level.
> > 
> > Now that we try to do perform satisfaction properly, we're sensitive to
> > the template nesting level even if the constraint is otherwise
> > non-dependent, because the template nesting level determines the level
> > of the auto that appears inside the constraint.  So we rely on
> > outer_targs to contain all levels of outer template arguments, because
> > we tack on another level to the end of outer_targs which needs to
> > map to the level of the auto for satisfaction.
> > 
> > (As a hacky workaround, when outer_targs is incomplete, can probably
> > just augment it with empty levels until it's TEMPLATE_TYPE_LEVEL(auto_node)-1
> > levels deep, which would fix the nested template case as long as the
> > constraint was depended only on the innermost level of template
> > arguments.)
> > 
> > > 
> > > > > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for
> > > > > trunk?  Also tested on range-v3 and cmcstl2.
> > > > 
> > > > Here's the same patch generated with -w which hides the noisy indentation
> > > > changes:
> > > > 
> > > > -- >8 --
> > > > 
> > > > 	PR c++/99365
> > > > 	* pt.c (do_auto_deduction): When processing_template_decl != 0
> > > > 	and context is adc_unify and we have constraints, pretend the
> > > > 	constraints are satisfied instead of punting.  Add some
> > > > 	clarifying sanity checks.  Don't substitute outer_targs into
> > > > 	type if not needed.
> > > > 
> > > > gcc/testsuite/ChangeLog:
> > > > 
> > > > 	PR c++/99365
> > > > 	* g++.dg/cpp2a/concepts-partial-spec9.C: New test.
> > > > ---
> > > >   gcc/cp/pt.c                                   | 24 ++++++++++++++-----
> > > >   .../g++.dg/cpp2a/concepts-partial-spec9.C     | 24 +++++++++++++++++++
> > > >   2 files changed, 42 insertions(+), 6 deletions(-)
> > > >   create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
> > > > 
> > > > diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
> > > > index a4686e0affb..ce537e4529a 100644
> > > > --- a/gcc/cp/pt.c
> > > > +++ b/gcc/cp/pt.c
> > > > @@ -23693,7 +23693,8 @@ unify (tree tparms, tree targs, tree parm, tree arg,
> > > > int strict,
> > > >     	  if (tree a = type_uses_auto (tparm))
> > > >   	    {
> > > > -	      tparm = do_auto_deduction (tparm, arg, a, complain, adc_unify);
> > > > +	      tparm = do_auto_deduction (tparm, arg, a,
> > > > +					 complain, adc_unify, targs);
> > > >   	      if (tparm == error_mark_node)
> > > >   		return 1;
> > > >   	    }
> > > > @@ -29619,13 +29620,21 @@ do_auto_deduction (tree type, tree init, tree
> > > > auto_node,
> > > >       }
> > > >       /* Check any placeholder constraints against the deduced type. */
> > > > -  if (flag_concepts)
> > > > -    if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
> > > > +  if (processing_template_decl && context == adc_unify)
> > > > +    /* Pretend constraints are satisfied.  */;
> > > > +  else if (flag_concepts
> > > > +	   && NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
> > > >       {
> > > >         if (processing_template_decl)
> > > > -	  /* In general we can't check satisfaction until we know all
> > > > -	     template arguments.  */
> > > > +	{
> > > > +	  /* Even though the initializer is non-dependent, we need to wait
> > > > until
> > > > +	     instantiation time to resolve this constrained placeholder
> > > > variable
> > > > +	     or return type, since the constraint itself may be dependent.  */
> > > 
> > > Can't we check whether the constraint is dependent, and check satisfaction if
> > > it isn't?  That might be necessary to make later expressions non-dependent
> > > that are supposed to be.
> > 
> > We'd have to check if outer_targs (and the deduced type) is dependent
> > too.  But it seems tricky because outer_targs, during adc_unify
> > deduction, is usually at least partially empty which is enough to make
> > any_dependent_template_arguments_p return true.
> 
> Ah sorry, I just realized you probably meant we should check whether the
> constraint is dependent during adc_variable_type and adc_return_type
> deduction.  I think that might be straightforward; I'll try it.  I'll
> also experiment with the hacky workaround mentioned earlier.

Here's a patch that incorporates these two ideas:

-- >8 --

Subject: [PATCH] c++: adc_unify deduction with constrained auto [PR99365]

My recent r11-7454 changed the way do_auto_deduction handles constrained
placeholders during template argument deduction (context == adc_unify)
when processing_template_decl != 0.

Before the patch, when processing_template_decl != 0 we would just
ignore the constraints on the placeholder in this situation, and proceed
with deduction:

  /* Check any placeholder constraints against the deduced type. */
  if (flag_concepts && !processing_template_decl)
    if (tree check = NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
      {
        ...

After the patch, we now punt and return the original placeholder type:

  /* Check any placeholder constraints against the deduced type. */
  if (flag_concepts)
    if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
      {
        if (processing_template_decl)
          /* In general we can't check satisfaction until we know all
             template arguments.  */
          return type;
        ...

While this change fixed instances where we'd prematurely resolve a
constrained placeholder return or variable type with non-dependent
initializer at template parse time (such as PR96444), it broke the
adc_unify callers that rely on the previous behavior.

So this patch restores the previous behavior during adc_unify deduction,
while retaining the new behavior only during adc_variable_type or
adc_return_type deduction.

We additionally now need to pass the outer template arguments to
do_auto_deduction during unify, for sake of constraint checking.
But we don't want do_auto_deduction to substitute these outer arguments
into type when the caller has already done so, so this patch adds
a TEMPLATE_TYPE_LEVEL check to do_auto_deduction to that effect.

Overall, these changes fix partial specialization of non-nested
templates with constrained 'auto' template parameters, but nested
templates are still broken, ultimately because
most_specialized_partial_spec passes only the innermost template
arguments to get_partial_spec_bindings, and so outer_targs during
do_auto_deduction (called from unify) contains only the innermost
template arguments which makes satisfaction unhappy.  Fixing this
properly is perhaps too risky at this stage, so this patch adds a hack
to do_auto_deduction to compensate for callers that don't supply all
outer template arguments.  The goal of this hack is to ensure
placeholder type constraint checking continues to work whenever it
worked before r11-7454, namely whenever the constraint is non-dependent.

Finally, this patch allows do_auto_deduction to resolve a constrained
placeholder type ahead of time (at template parse time), as long as the
constraint is non-dependent.

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

gcc/cp/ChangeLog:

	PR c++/99365
	* pt.c (do_auto_deduction): When processing_template_decl != 0
	and context is adc_unify and we have constraints, pretend the
	constraints are satisfied instead of punting.  Otherwise don't
	punt unless any of the explicit arguments in the constraint are
	dependent.  Add some clarifying sanity checks.  Add a hack to
	add missing outermost template levels to outer_args before
	checking satisfaction.  Don't substitute outer_targs into type
	if it's already been done.

gcc/testsuite/ChangeLog:

	PR c++/99365
	* g++.dg/cpp2a/concepts-partial-spec9.C: New test.
	* g++.dg/cpp2a/concepts-placeholder4: New test.
---
 gcc/cp/pt.c                                   | 44 ++++++++++++++++---
 .../g++.dg/cpp2a/concepts-partial-spec9.C     | 23 ++++++++++
 .../g++.dg/cpp2a/concepts-placeholder4.C      | 24 ++++++++++
 3 files changed, 85 insertions(+), 6 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-placeholder4.C

diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
index 83589101c0d..31386a74a59 100644
--- a/gcc/cp/pt.c
+++ b/gcc/cp/pt.c
@@ -23681,7 +23681,8 @@ unify (tree tparms, tree targs, tree parm, tree arg, int strict,
 
 	  if (tree a = type_uses_auto (tparm))
 	    {
-	      tparm = do_auto_deduction (tparm, arg, a, complain, adc_unify);
+	      tparm = do_auto_deduction (tparm, arg, a,
+					 complain, adc_unify, targs);
 	      if (tparm == error_mark_node)
 		return 1;
 	    }
@@ -29611,13 +29612,24 @@ do_auto_deduction (tree type, tree init, tree auto_node,
     }
 
   /* Check any placeholder constraints against the deduced type. */
-  if (flag_concepts)
-    if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
+  if (processing_template_decl && context == adc_unify)
+    /* Pretend constraints are satisfied.  */;
+  else if (tree constr = NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
     {
       if (processing_template_decl)
-	  /* In general we can't check satisfaction until we know all
-	     template arguments.  */
+	{
+	  gcc_checking_assert (context == adc_variable_type
+			       || context == adc_return_type);
+	  gcc_checking_assert (!type_dependent_expression_p (init));
+	  /* Check if any of the explicit arguments in the constraint
+	     are dependent.  If so, we need to wait until instantiation time
+	     to resolve the constriant.  */
+	  tree cid = unpack_concept_check (constr);
+	  tree cargs = TREE_OPERAND (cid, 1);
+	  for (int i = 1; i < TREE_VEC_LENGTH (cargs); ++i)
+	    if (dependent_template_arg_p (TREE_VEC_ELT (cargs, i)))
 	      return type;
+	}
 
       if ((context == adc_return_type || context == adc_variable_type)
 	  && current_function_decl
@@ -29625,6 +29637,23 @@ do_auto_deduction (tree type, tree init, tree auto_node,
 	outer_targs = DECL_TI_ARGS (current_function_decl);
 
       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
+	 good subset of valid 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 (!constraints_satisfied_p (auto_node, full_targs))
 	{
 	  if (complain & tf_warning_or_error)
@@ -29656,7 +29685,10 @@ do_auto_deduction (tree type, tree init, tree auto_node,
 	}
     }
 
-  if (context == adc_unify)
+  if (TEMPLATE_TYPE_LEVEL (auto_node) == 1)
+    /* The outer template arguments are already substituted into type
+       (but we still may have used them for constraint checking above).  */;
+  else if (context == adc_unify)
     targs = add_to_template_args (outer_targs, targs);
   else if (processing_template_decl)
     targs = add_to_template_args (current_template_args (), targs);
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C b/gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
new file mode 100644
index 00000000000..3dae24d915a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
@@ -0,0 +1,23 @@
+// PR c++/99365
+// { dg-do compile { target c++20 } }
+
+template <class> concept C = true;
+template <class T, class U> concept D = C<T> && __is_same(T, U);
+
+template <class, C auto> struct A { static const int i = 0; };
+template <class T, D<T> auto V> struct A<T, V> { static const int i = 1; };
+
+static_assert(A<int, 0>::i == 1);
+static_assert(A<char, 0>::i == 0);
+static_assert(A<int, '0'>::i == 0);
+static_assert(A<char, '0'>::i == 1);
+
+template <class> struct O {
+  template <class, C auto> struct A { static const int i = 0; };
+  template <class T, D<T> auto V> struct A<T, V> { static const int i = 1; };
+};
+
+static_assert(O<void>::A<int, 0>::i == 1);
+static_assert(O<void>::A<char, 0>::i == 0);
+static_assert(O<void>::A<int, '0'>::i == 0);
+static_assert(O<void>::A<char, '0'>::i == 1);
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder4.C b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder4.C
new file mode 100644
index 00000000000..082c5d36cbe
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder4.C
@@ -0,0 +1,24 @@
+// { dg-do compile { target c++20 } }
+
+template <class T, class U> concept same_as = __is_same(T, U);
+
+// non-dependent initializer, always-satisfied constraint resolved at parse time
+template <class T> same_as<bool> auto x = true;
+
+template <auto V> same_as<int> auto y = V; // { dg-error "constraint" }
+
+template <class>
+struct A {
+  template <auto V> static inline same_as<int> auto z = V; // { dg-error "constraint" }
+};
+
+int main() {
+  x<int>;          // { dg-bogus "" }
+  y<0>;            // { dg-bogus "" }
+  y<'0'>;          // { dg-message "required from here" }
+  A<void>::z<0>;   // { dg-bogus "" }
+  A<void>::z<'0'>; // { dg-message "required from here" }
+}
+
+// non-dependent initializer, never-satisfied constraint diagnosed at parse time
+template <class T> same_as<int> auto z = true; // { dg-error "constraint" }
-- 
2.31.0.rc0.75.gec125d1bc1


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH] c++: adc_unify deduction with constrained auto [PR99365]
  2021-03-05  2:55         ` Patrick Palka
@ 2021-03-05 19:12           ` Jason Merrill
  2021-03-05 20:42             ` Patrick Palka
  0 siblings, 1 reply; 9+ messages in thread
From: Jason Merrill @ 2021-03-05 19:12 UTC (permalink / raw)
  To: Patrick Palka; +Cc: gcc-patches

On 3/4/21 9:55 PM, Patrick Palka wrote:
> On Thu, 4 Mar 2021, Patrick Palka wrote:
> 
>> On Thu, 4 Mar 2021, Patrick Palka wrote:
>>
>>> On Thu, 4 Mar 2021, Jason Merrill wrote:
>>>
>>>> On 3/4/21 11:32 AM, Patrick Palka wrote:
>>>>> On Thu, 4 Mar 2021, Patrick Palka wrote:
>>>>>
>>>>>> My recent r11-7454 changed the way do_auto_deduction handles constrained
>>>>>> placeholders during template argument deduction (context == adc_unify)
>>>>>> when processing_template_decl != 0.
>>>>>>
>>>>>> Before the patch, when processing_template_decl != 0 we would just
>>>>>> ignore the constraints on the placeholder in this situation, and proceed
>>>>>> with deduction:
>>>>>>
>>>>>>     /* Check any placeholder constraints against the deduced type. */
>>>>>>     if (flag_concepts && !processing_template_decl)
>>>>>>       if (tree check = NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS
>>>>>> (auto_node)))
>>>>>>         {
>>>>>>           ...
>>>>>>
>>>>>> After the patch, we now punt and return the original placeholder type:
>>>>>>
>>>>>>     /* Check any placeholder constraints against the deduced type. */
>>>>>>     if (flag_concepts)
>>>>>>       if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
>>>>>>         {
>>>>>>           if (processing_template_decl)
>>>>>>             /* In general we can't check satisfaction until we know all
>>>>>>                template arguments.  */
>>>>>>             return type;
>>>>>>           ...
>>>>>>
>>>>>> While this change fixed instances where we'd prematurely resolve a
>>>>>> constrained placeholder return or variable type with non-dependent
>>>>>> initializer at template parse time (such as PR96444), it broke the
>>>>>> adc_unify callers that rely on this previous behavior.
>>>>>>
>>>>>> So this patch restores the previous behavior during adc_unify deduction
>>>>>> while retaining the new behavior only during adc_variable_type or
>>>>>> adc_return_type deduction.
>>>>
>>>> Sure, it makes sense for adc_unify to behave differently, since in deduction
>>>> context constraints are checked after all template args have been deduced.
>>>
>>> I see.
>>>
>>>>
>>>>>> We additionally now need to pass outer template arguments to
>>>>>> do_auto_deduction during unify, for sake of constraint checking.
>>>>>> But we don't want do_auto_deduction to substitute these outer arguments
>>>>>> into type if it's already been done, hence the added TEMPLATE_TYPE_LEVEL
>>>>>> check.
>>>>>>
>>>>>> This fixes partial specializations of non-nested templates with
>>>>>> constrained 'auto' template parameters, but nested templates are still
>>>>>> broken, ultimately because most_specialized_partial_spec passes only the
>>>>>> innermost template arguments to get_partial_spec_bindings, and so
>>>>>> outer_targs during do_auto_deduction (called from unify) contains only
>>>>>> the innermost template arguments which makes satisfaction unhappy.
>>>>>> Fixing this might be too invasive at this stage, perhaps..  (Seems we
>>>>>> need to make most_specialized_partial_spec pass all template arguments
>>>>>> to get_partial_spec_bindings.)
>>>>
>>>> How did this work before?
>>>
>>> Before, it would work, but only if the constraint didn't also depend on
>>> any outer template arguments.  do_auto_deduction would just "surgically"
>>> replace the auto in the concept-id with the type we deduced and leave
>>> the other template arguments of the concept-id alone.  So if the
>>> constraint was non-dependent, satisfaction would work regardless of the
>>> template nesting level.
>>>
>>> Now that we try to do perform satisfaction properly, we're sensitive to
>>> the template nesting level even if the constraint is otherwise
>>> non-dependent, because the template nesting level determines the level
>>> of the auto that appears inside the constraint.  So we rely on
>>> outer_targs to contain all levels of outer template arguments, because
>>> we tack on another level to the end of outer_targs which needs to
>>> map to the level of the auto for satisfaction.
>>>
>>> (As a hacky workaround, when outer_targs is incomplete, can probably
>>> just augment it with empty levels until it's TEMPLATE_TYPE_LEVEL(auto_node)-1
>>> levels deep, which would fix the nested template case as long as the
>>> constraint was depended only on the innermost level of template
>>> arguments.)
>>>
>>>>
>>>>>> Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for
>>>>>> trunk?  Also tested on range-v3 and cmcstl2.
>>>>>
>>>>> Here's the same patch generated with -w which hides the noisy indentation
>>>>> changes:
>>>>>
>>>>> -- >8 --
>>>>>
>>>>> 	PR c++/99365
>>>>> 	* pt.c (do_auto_deduction): When processing_template_decl != 0
>>>>> 	and context is adc_unify and we have constraints, pretend the
>>>>> 	constraints are satisfied instead of punting.  Add some
>>>>> 	clarifying sanity checks.  Don't substitute outer_targs into
>>>>> 	type if not needed.
>>>>>
>>>>> gcc/testsuite/ChangeLog:
>>>>>
>>>>> 	PR c++/99365
>>>>> 	* g++.dg/cpp2a/concepts-partial-spec9.C: New test.
>>>>> ---
>>>>>    gcc/cp/pt.c                                   | 24 ++++++++++++++-----
>>>>>    .../g++.dg/cpp2a/concepts-partial-spec9.C     | 24 +++++++++++++++++++
>>>>>    2 files changed, 42 insertions(+), 6 deletions(-)
>>>>>    create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
>>>>>
>>>>> diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
>>>>> index a4686e0affb..ce537e4529a 100644
>>>>> --- a/gcc/cp/pt.c
>>>>> +++ b/gcc/cp/pt.c
>>>>> @@ -23693,7 +23693,8 @@ unify (tree tparms, tree targs, tree parm, tree arg,
>>>>> int strict,
>>>>>      	  if (tree a = type_uses_auto (tparm))
>>>>>    	    {
>>>>> -	      tparm = do_auto_deduction (tparm, arg, a, complain, adc_unify);
>>>>> +	      tparm = do_auto_deduction (tparm, arg, a,
>>>>> +					 complain, adc_unify, targs);
>>>>>    	      if (tparm == error_mark_node)
>>>>>    		return 1;
>>>>>    	    }
>>>>> @@ -29619,13 +29620,21 @@ do_auto_deduction (tree type, tree init, tree
>>>>> auto_node,
>>>>>        }
>>>>>        /* Check any placeholder constraints against the deduced type. */
>>>>> -  if (flag_concepts)
>>>>> -    if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
>>>>> +  if (processing_template_decl && context == adc_unify)
>>>>> +    /* Pretend constraints are satisfied.  */;
>>>>> +  else if (flag_concepts
>>>>> +	   && NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
>>>>>        {
>>>>>          if (processing_template_decl)
>>>>> -	  /* In general we can't check satisfaction until we know all
>>>>> -	     template arguments.  */
>>>>> +	{
>>>>> +	  /* Even though the initializer is non-dependent, we need to wait
>>>>> until
>>>>> +	     instantiation time to resolve this constrained placeholder
>>>>> variable
>>>>> +	     or return type, since the constraint itself may be dependent.  */
>>>>
>>>> Can't we check whether the constraint is dependent, and check satisfaction if
>>>> it isn't?  That might be necessary to make later expressions non-dependent
>>>> that are supposed to be.
>>>
>>> We'd have to check if outer_targs (and the deduced type) is dependent
>>> too.  But it seems tricky because outer_targs, during adc_unify
>>> deduction, is usually at least partially empty which is enough to make
>>> any_dependent_template_arguments_p return true.
>>
>> Ah sorry, I just realized you probably meant we should check whether the
>> constraint is dependent during adc_variable_type and adc_return_type
>> deduction.  I think that might be straightforward; I'll try it.  I'll
>> also experiment with the hacky workaround mentioned earlier.
> 
> Here's a patch that incorporates these two ideas:
> 
> -- >8 --
> 
> Subject: [PATCH] c++: adc_unify deduction with constrained auto [PR99365]
> 
> My recent r11-7454 changed the way do_auto_deduction handles constrained
> placeholders during template argument deduction (context == adc_unify)
> when processing_template_decl != 0.
> 
> Before the patch, when processing_template_decl != 0 we would just
> ignore the constraints on the placeholder in this situation, and proceed
> with deduction:
> 
>    /* Check any placeholder constraints against the deduced type. */
>    if (flag_concepts && !processing_template_decl)
>      if (tree check = NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
>        {
>          ...
> 
> After the patch, we now punt and return the original placeholder type:
> 
>    /* Check any placeholder constraints against the deduced type. */
>    if (flag_concepts)
>      if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
>        {
>          if (processing_template_decl)
>            /* In general we can't check satisfaction until we know all
>               template arguments.  */
>            return type;
>          ...
> 
> While this change fixed instances where we'd prematurely resolve a
> constrained placeholder return or variable type with non-dependent
> initializer at template parse time (such as PR96444), it broke the
> adc_unify callers that rely on the previous behavior.
> 
> So this patch restores the previous behavior during adc_unify deduction,
> while retaining the new behavior only during adc_variable_type or
> adc_return_type deduction.
> 
> We additionally now need to pass the outer template arguments to
> do_auto_deduction during unify, for sake of constraint checking.
> But we don't want do_auto_deduction to substitute these outer arguments
> into type when the caller has already done so, so this patch adds
> a TEMPLATE_TYPE_LEVEL check to do_auto_deduction to that effect.
> 
> Overall, these changes fix partial specialization of non-nested
> templates with constrained 'auto' template parameters, but nested
> templates are still broken, ultimately because
> most_specialized_partial_spec passes only the innermost template
> arguments to get_partial_spec_bindings, and so outer_targs during
> do_auto_deduction (called from unify) contains only the innermost
> template arguments which makes satisfaction unhappy.  Fixing this
> properly is perhaps too risky at this stage, so this patch adds a hack
> to do_auto_deduction to compensate for callers that don't supply all
> outer template arguments.  The goal of this hack is to ensure
> placeholder type constraint checking continues to work whenever it
> worked before r11-7454, namely whenever the constraint is non-dependent.
> 
> Finally, this patch allows do_auto_deduction to resolve a constrained
> placeholder type ahead of time (at template parse time), as long as the
> constraint is non-dependent.
> 
> Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for
> trunk?  Also tested on range-v3 and cmcstl2.
> 
> gcc/cp/ChangeLog:
> 
> 	PR c++/99365
> 	* pt.c (do_auto_deduction): When processing_template_decl != 0
> 	and context is adc_unify and we have constraints, pretend the
> 	constraints are satisfied instead of punting.  Otherwise don't
> 	punt unless any of the explicit arguments in the constraint are
> 	dependent.  Add some clarifying sanity checks.  Add a hack to
> 	add missing outermost template levels to outer_args before
> 	checking satisfaction.  Don't substitute outer_targs into type
> 	if it's already been done.
> 
> gcc/testsuite/ChangeLog:
> 
> 	PR c++/99365
> 	* g++.dg/cpp2a/concepts-partial-spec9.C: New test.
> 	* g++.dg/cpp2a/concepts-placeholder4: New test.
> ---
>   gcc/cp/pt.c                                   | 44 ++++++++++++++++---
>   .../g++.dg/cpp2a/concepts-partial-spec9.C     | 23 ++++++++++
>   .../g++.dg/cpp2a/concepts-placeholder4.C      | 24 ++++++++++
>   3 files changed, 85 insertions(+), 6 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-placeholder4.C
> 
> diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
> index 83589101c0d..31386a74a59 100644
> --- a/gcc/cp/pt.c
> +++ b/gcc/cp/pt.c
> @@ -23681,7 +23681,8 @@ unify (tree tparms, tree targs, tree parm, tree arg, int strict,
>   
>   	  if (tree a = type_uses_auto (tparm))
>   	    {
> -	      tparm = do_auto_deduction (tparm, arg, a, complain, adc_unify);
> +	      tparm = do_auto_deduction (tparm, arg, a,
> +					 complain, adc_unify, targs);
>   	      if (tparm == error_mark_node)
>   		return 1;
>   	    }
> @@ -29611,13 +29612,24 @@ do_auto_deduction (tree type, tree init, tree auto_node,
>       }
>   
>     /* Check any placeholder constraints against the deduced type. */
> -  if (flag_concepts)
> -    if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
> +  if (processing_template_decl && context == adc_unify)
> +    /* Pretend constraints are satisfied.  */;

Let's say "constraints will be checked after deduction".

> +  else if (tree constr = NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
>       {
>         if (processing_template_decl)
> -	  /* In general we can't check satisfaction until we know all
> -	     template arguments.  */
> +	{
> +	  gcc_checking_assert (context == adc_variable_type
> +			       || context == adc_return_type);
> +	  gcc_checking_assert (!type_dependent_expression_p (init));

This assert seems redundant with the condition at the top of the 
function, though I suppose it's harmless to have it here as well.

> +	  /* Check if any of the explicit arguments in the constraint
> +	     are dependent.  If so, we need to wait until instantiation time
> +	     to resolve the constriant.  */
> +	  tree cid = unpack_concept_check (constr);
> +	  tree cargs = TREE_OPERAND (cid, 1);
> +	  for (int i = 1; i < TREE_VEC_LENGTH (cargs); ++i)
> +	    if (dependent_template_arg_p (TREE_VEC_ELT (cargs, i)))
>   	      return type;

Let's factor this out; I imagine testing whether a concept-check is 
dependent (apart from the placeholder type itself) may be more broadly 
useful.

> +	}
>   
>         if ((context == adc_return_type || context == adc_variable_type)
>   	  && current_function_decl
> @@ -29625,6 +29637,23 @@ do_auto_deduction (tree type, tree init, tree auto_node,
>   	outer_targs = DECL_TI_ARGS (current_function_decl);
>   
>         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
> +	 good subset of valid 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 (!constraints_satisfied_p (auto_node, full_targs))
>   	{
>   	  if (complain & tf_warning_or_error)
> @@ -29656,7 +29685,10 @@ do_auto_deduction (tree type, tree init, tree auto_node,
>   	}
>       }
>   
> -  if (context == adc_unify)
> +  if (TEMPLATE_TYPE_LEVEL (auto_node) == 1)
> +    /* The outer template arguments are already substituted into type
> +       (but we still may have used them for constraint checking above).  */;
> +  else if (context == adc_unify)
>       targs = add_to_template_args (outer_targs, targs);
>     else if (processing_template_decl)
>       targs = add_to_template_args (current_template_args (), targs);
> diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C b/gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
> new file mode 100644
> index 00000000000..3dae24d915a
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
> @@ -0,0 +1,23 @@
> +// PR c++/99365
> +// { dg-do compile { target c++20 } }
> +
> +template <class> concept C = true;
> +template <class T, class U> concept D = C<T> && __is_same(T, U);
> +
> +template <class, C auto> struct A { static const int i = 0; };
> +template <class T, D<T> auto V> struct A<T, V> { static const int i = 1; };
> +
> +static_assert(A<int, 0>::i == 1);
> +static_assert(A<char, 0>::i == 0);
> +static_assert(A<int, '0'>::i == 0);
> +static_assert(A<char, '0'>::i == 1);
> +
> +template <class> struct O {
> +  template <class, C auto> struct A { static const int i = 0; };
> +  template <class T, D<T> auto V> struct A<T, V> { static const int i = 1; };
> +};
> +
> +static_assert(O<void>::A<int, 0>::i == 1);
> +static_assert(O<void>::A<char, 0>::i == 0);
> +static_assert(O<void>::A<int, '0'>::i == 0);
> +static_assert(O<void>::A<char, '0'>::i == 1);
> diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder4.C b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder4.C
> new file mode 100644
> index 00000000000..082c5d36cbe
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder4.C
> @@ -0,0 +1,24 @@
> +// { dg-do compile { target c++20 } }
> +
> +template <class T, class U> concept same_as = __is_same(T, U);
> +
> +// non-dependent initializer, always-satisfied constraint resolved at parse time
> +template <class T> same_as<bool> auto x = true;
> +
> +template <auto V> same_as<int> auto y = V; // { dg-error "constraint" }
> +
> +template <class>
> +struct A {
> +  template <auto V> static inline same_as<int> auto z = V; // { dg-error "constraint" }
> +};
> +
> +int main() {
> +  x<int>;          // { dg-bogus "" }
> +  y<0>;            // { dg-bogus "" }
> +  y<'0'>;          // { dg-message "required from here" }
> +  A<void>::z<0>;   // { dg-bogus "" }
> +  A<void>::z<'0'>; // { dg-message "required from here" }
> +}
> +
> +// non-dependent initializer, never-satisfied constraint diagnosed at parse time
> +template <class T> same_as<int> auto z = true; // { dg-error "constraint" }
> 


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH] c++: adc_unify deduction with constrained auto [PR99365]
  2021-03-05 19:12           ` Jason Merrill
@ 2021-03-05 20:42             ` Patrick Palka
  2021-03-05 21:14               ` Jason Merrill
  0 siblings, 1 reply; 9+ messages in thread
From: Patrick Palka @ 2021-03-05 20:42 UTC (permalink / raw)
  To: Jason Merrill; +Cc: Patrick Palka, gcc-patches

On Fri, 5 Mar 2021, Jason Merrill wrote:

> On 3/4/21 9:55 PM, Patrick Palka wrote:
> > On Thu, 4 Mar 2021, Patrick Palka wrote:
> > 
> > > On Thu, 4 Mar 2021, Patrick Palka wrote:
> > > 
> > > > On Thu, 4 Mar 2021, Jason Merrill wrote:
> > > > 
> > > > > On 3/4/21 11:32 AM, Patrick Palka wrote:
> > > > > > On Thu, 4 Mar 2021, Patrick Palka wrote:
> > > > > > 
> > > > > > > My recent r11-7454 changed the way do_auto_deduction handles
> > > > > > > constrained
> > > > > > > placeholders during template argument deduction (context ==
> > > > > > > adc_unify)
> > > > > > > when processing_template_decl != 0.
> > > > > > > 
> > > > > > > Before the patch, when processing_template_decl != 0 we would just
> > > > > > > ignore the constraints on the placeholder in this situation, and
> > > > > > > proceed
> > > > > > > with deduction:
> > > > > > > 
> > > > > > >     /* Check any placeholder constraints against the deduced type.
> > > > > > > */
> > > > > > >     if (flag_concepts && !processing_template_decl)
> > > > > > >       if (tree check = NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS
> > > > > > > (auto_node)))
> > > > > > >         {
> > > > > > >           ...
> > > > > > > 
> > > > > > > After the patch, we now punt and return the original placeholder
> > > > > > > type:
> > > > > > > 
> > > > > > >     /* Check any placeholder constraints against the deduced type.
> > > > > > > */
> > > > > > >     if (flag_concepts)
> > > > > > >       if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
> > > > > > >         {
> > > > > > >           if (processing_template_decl)
> > > > > > >             /* In general we can't check satisfaction until we
> > > > > > > know all
> > > > > > >                template arguments.  */
> > > > > > >             return type;
> > > > > > >           ...
> > > > > > > 
> > > > > > > While this change fixed instances where we'd prematurely resolve a
> > > > > > > constrained placeholder return or variable type with non-dependent
> > > > > > > initializer at template parse time (such as PR96444), it broke the
> > > > > > > adc_unify callers that rely on this previous behavior.
> > > > > > > 
> > > > > > > So this patch restores the previous behavior during adc_unify
> > > > > > > deduction
> > > > > > > while retaining the new behavior only during adc_variable_type or
> > > > > > > adc_return_type deduction.
> > > > > 
> > > > > Sure, it makes sense for adc_unify to behave differently, since in
> > > > > deduction
> > > > > context constraints are checked after all template args have been
> > > > > deduced.
> > > > 
> > > > I see.
> > > > 
> > > > > 
> > > > > > > We additionally now need to pass outer template arguments to
> > > > > > > do_auto_deduction during unify, for sake of constraint checking.
> > > > > > > But we don't want do_auto_deduction to substitute these outer
> > > > > > > arguments
> > > > > > > into type if it's already been done, hence the added
> > > > > > > TEMPLATE_TYPE_LEVEL
> > > > > > > check.
> > > > > > > 
> > > > > > > This fixes partial specializations of non-nested templates with
> > > > > > > constrained 'auto' template parameters, but nested templates are
> > > > > > > still
> > > > > > > broken, ultimately because most_specialized_partial_spec passes
> > > > > > > only the
> > > > > > > innermost template arguments to get_partial_spec_bindings, and so
> > > > > > > outer_targs during do_auto_deduction (called from unify) contains
> > > > > > > only
> > > > > > > the innermost template arguments which makes satisfaction unhappy.
> > > > > > > Fixing this might be too invasive at this stage, perhaps..  (Seems
> > > > > > > we
> > > > > > > need to make most_specialized_partial_spec pass all template
> > > > > > > arguments
> > > > > > > to get_partial_spec_bindings.)
> > > > > 
> > > > > How did this work before?
> > > > 
> > > > Before, it would work, but only if the constraint didn't also depend on
> > > > any outer template arguments.  do_auto_deduction would just "surgically"
> > > > replace the auto in the concept-id with the type we deduced and leave
> > > > the other template arguments of the concept-id alone.  So if the
> > > > constraint was non-dependent, satisfaction would work regardless of the
> > > > template nesting level.
> > > > 
> > > > Now that we try to do perform satisfaction properly, we're sensitive to
> > > > the template nesting level even if the constraint is otherwise
> > > > non-dependent, because the template nesting level determines the level
> > > > of the auto that appears inside the constraint.  So we rely on
> > > > outer_targs to contain all levels of outer template arguments, because
> > > > we tack on another level to the end of outer_targs which needs to
> > > > map to the level of the auto for satisfaction.
> > > > 
> > > > (As a hacky workaround, when outer_targs is incomplete, can probably
> > > > just augment it with empty levels until it's
> > > > TEMPLATE_TYPE_LEVEL(auto_node)-1
> > > > levels deep, which would fix the nested template case as long as the
> > > > constraint was depended only on the innermost level of template
> > > > arguments.)
> > > > 
> > > > > 
> > > > > > > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look
> > > > > > > OK for
> > > > > > > trunk?  Also tested on range-v3 and cmcstl2.
> > > > > > 
> > > > > > Here's the same patch generated with -w which hides the noisy
> > > > > > indentation
> > > > > > changes:
> > > > > > 
> > > > > > -- >8 --
> > > > > > 
> > > > > > 	PR c++/99365
> > > > > > 	* pt.c (do_auto_deduction): When processing_template_decl != 0
> > > > > > 	and context is adc_unify and we have constraints, pretend the
> > > > > > 	constraints are satisfied instead of punting.  Add some
> > > > > > 	clarifying sanity checks.  Don't substitute outer_targs into
> > > > > > 	type if not needed.
> > > > > > 
> > > > > > gcc/testsuite/ChangeLog:
> > > > > > 
> > > > > > 	PR c++/99365
> > > > > > 	* g++.dg/cpp2a/concepts-partial-spec9.C: New test.
> > > > > > ---
> > > > > >    gcc/cp/pt.c                                   | 24
> > > > > > ++++++++++++++-----
> > > > > >    .../g++.dg/cpp2a/concepts-partial-spec9.C     | 24
> > > > > > +++++++++++++++++++
> > > > > >    2 files changed, 42 insertions(+), 6 deletions(-)
> > > > > >    create mode 100644
> > > > > > gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
> > > > > > 
> > > > > > diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
> > > > > > index a4686e0affb..ce537e4529a 100644
> > > > > > --- a/gcc/cp/pt.c
> > > > > > +++ b/gcc/cp/pt.c
> > > > > > @@ -23693,7 +23693,8 @@ unify (tree tparms, tree targs, tree parm,
> > > > > > tree arg,
> > > > > > int strict,
> > > > > >      	  if (tree a = type_uses_auto (tparm))
> > > > > >    	    {
> > > > > > -	      tparm = do_auto_deduction (tparm, arg, a, complain,
> > > > > > adc_unify);
> > > > > > +	      tparm = do_auto_deduction (tparm, arg, a,
> > > > > > +					 complain, adc_unify, targs);
> > > > > >    	      if (tparm == error_mark_node)
> > > > > >    		return 1;
> > > > > >    	    }
> > > > > > @@ -29619,13 +29620,21 @@ do_auto_deduction (tree type, tree init,
> > > > > > tree
> > > > > > auto_node,
> > > > > >        }
> > > > > >        /* Check any placeholder constraints against the deduced
> > > > > > type. */
> > > > > > -  if (flag_concepts)
> > > > > > -    if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
> > > > > > +  if (processing_template_decl && context == adc_unify)
> > > > > > +    /* Pretend constraints are satisfied.  */;
> > > > > > +  else if (flag_concepts
> > > > > > +	   && NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
> > > > > >        {
> > > > > >          if (processing_template_decl)
> > > > > > -	  /* In general we can't check satisfaction until we know all
> > > > > > -	     template arguments.  */
> > > > > > +	{
> > > > > > +	  /* Even though the initializer is non-dependent, we need to
> > > > > > wait
> > > > > > until
> > > > > > +	     instantiation time to resolve this constrained
> > > > > > placeholder
> > > > > > variable
> > > > > > +	     or return type, since the constraint itself may be
> > > > > > dependent.  */
> > > > > 
> > > > > Can't we check whether the constraint is dependent, and check
> > > > > satisfaction if
> > > > > it isn't?  That might be necessary to make later expressions
> > > > > non-dependent
> > > > > that are supposed to be.
> > > > 
> > > > We'd have to check if outer_targs (and the deduced type) is dependent
> > > > too.  But it seems tricky because outer_targs, during adc_unify
> > > > deduction, is usually at least partially empty which is enough to make
> > > > any_dependent_template_arguments_p return true.
> > > 
> > > Ah sorry, I just realized you probably meant we should check whether the
> > > constraint is dependent during adc_variable_type and adc_return_type
> > > deduction.  I think that might be straightforward; I'll try it.  I'll
> > > also experiment with the hacky workaround mentioned earlier.
> > 
> > Here's a patch that incorporates these two ideas:
> > 
> > -- >8 --
> > 
> > Subject: [PATCH] c++: adc_unify deduction with constrained auto [PR99365]
> > 
> > My recent r11-7454 changed the way do_auto_deduction handles constrained
> > placeholders during template argument deduction (context == adc_unify)
> > when processing_template_decl != 0.
> > 
> > Before the patch, when processing_template_decl != 0 we would just
> > ignore the constraints on the placeholder in this situation, and proceed
> > with deduction:
> > 
> >    /* Check any placeholder constraints against the deduced type. */
> >    if (flag_concepts && !processing_template_decl)
> >      if (tree check = NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
> >        {
> >          ...
> > 
> > After the patch, we now punt and return the original placeholder type:
> > 
> >    /* Check any placeholder constraints against the deduced type. */
> >    if (flag_concepts)
> >      if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
> >        {
> >          if (processing_template_decl)
> >            /* In general we can't check satisfaction until we know all
> >               template arguments.  */
> >            return type;
> >          ...
> > 
> > While this change fixed instances where we'd prematurely resolve a
> > constrained placeholder return or variable type with non-dependent
> > initializer at template parse time (such as PR96444), it broke the
> > adc_unify callers that rely on the previous behavior.
> > 
> > So this patch restores the previous behavior during adc_unify deduction,
> > while retaining the new behavior only during adc_variable_type or
> > adc_return_type deduction.
> > 
> > We additionally now need to pass the outer template arguments to
> > do_auto_deduction during unify, for sake of constraint checking.
> > But we don't want do_auto_deduction to substitute these outer arguments
> > into type when the caller has already done so, so this patch adds
> > a TEMPLATE_TYPE_LEVEL check to do_auto_deduction to that effect.
> > 
> > Overall, these changes fix partial specialization of non-nested
> > templates with constrained 'auto' template parameters, but nested
> > templates are still broken, ultimately because
> > most_specialized_partial_spec passes only the innermost template
> > arguments to get_partial_spec_bindings, and so outer_targs during
> > do_auto_deduction (called from unify) contains only the innermost
> > template arguments which makes satisfaction unhappy.  Fixing this
> > properly is perhaps too risky at this stage, so this patch adds a hack
> > to do_auto_deduction to compensate for callers that don't supply all
> > outer template arguments.  The goal of this hack is to ensure
> > placeholder type constraint checking continues to work whenever it
> > worked before r11-7454, namely whenever the constraint is non-dependent.
> > 
> > Finally, this patch allows do_auto_deduction to resolve a constrained
> > placeholder type ahead of time (at template parse time), as long as the
> > constraint is non-dependent.
> > 
> > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for
> > trunk?  Also tested on range-v3 and cmcstl2.
> > 
> > gcc/cp/ChangeLog:
> > 
> > 	PR c++/99365
> > 	* pt.c (do_auto_deduction): When processing_template_decl != 0
> > 	and context is adc_unify and we have constraints, pretend the
> > 	constraints are satisfied instead of punting.  Otherwise don't
> > 	punt unless any of the explicit arguments in the constraint are
> > 	dependent.  Add some clarifying sanity checks.  Add a hack to
> > 	add missing outermost template levels to outer_args before
> > 	checking satisfaction.  Don't substitute outer_targs into type
> > 	if it's already been done.
> > 
> > gcc/testsuite/ChangeLog:
> > 
> > 	PR c++/99365
> > 	* g++.dg/cpp2a/concepts-partial-spec9.C: New test.
> > 	* g++.dg/cpp2a/concepts-placeholder4: New test.
> > ---
> >   gcc/cp/pt.c                                   | 44 ++++++++++++++++---
> >   .../g++.dg/cpp2a/concepts-partial-spec9.C     | 23 ++++++++++
> >   .../g++.dg/cpp2a/concepts-placeholder4.C      | 24 ++++++++++
> >   3 files changed, 85 insertions(+), 6 deletions(-)
> >   create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
> >   create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-placeholder4.C
> > 
> > diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
> > index 83589101c0d..31386a74a59 100644
> > --- a/gcc/cp/pt.c
> > +++ b/gcc/cp/pt.c
> > @@ -23681,7 +23681,8 @@ unify (tree tparms, tree targs, tree parm, tree arg,
> > int strict,
> >     	  if (tree a = type_uses_auto (tparm))
> >   	    {
> > -	      tparm = do_auto_deduction (tparm, arg, a, complain, adc_unify);
> > +	      tparm = do_auto_deduction (tparm, arg, a,
> > +					 complain, adc_unify, targs);
> >   	      if (tparm == error_mark_node)
> >   		return 1;
> >   	    }
> > @@ -29611,13 +29612,24 @@ do_auto_deduction (tree type, tree init, tree
> > auto_node,
> >       }
> >       /* Check any placeholder constraints against the deduced type. */
> > -  if (flag_concepts)
> > -    if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
> > +  if (processing_template_decl && context == adc_unify)
> > +    /* Pretend constraints are satisfied.  */;
> 
> Let's say "constraints will be checked after deduction".

Done.

> 
> > +  else if (tree constr = NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS
> > (auto_node)))
> >       {
> >         if (processing_template_decl)
> > -	  /* In general we can't check satisfaction until we know all
> > -	     template arguments.  */
> > +	{
> > +	  gcc_checking_assert (context == adc_variable_type
> > +			       || context == adc_return_type);
> > +	  gcc_checking_assert (!type_dependent_expression_p (init));
> 
> This assert seems redundant with the condition at the top of the function,
> though I suppose it's harmless to have it here as well.

Yeah, I figured the assert might be beneficial to the reader, as a
reminder that we're dealing with a non-dependent initializer, since the
condition is a bit far away at this point.

> 
> > +	  /* Check if any of the explicit arguments in the constraint
> > +	     are dependent.  If so, we need to wait until instantiation time
> > +	     to resolve the constriant.  */
> > +	  tree cid = unpack_concept_check (constr);
> > +	  tree cargs = TREE_OPERAND (cid, 1);
> > +	  for (int i = 1; i < TREE_VEC_LENGTH (cargs); ++i)
> > +	    if (dependent_template_arg_p (TREE_VEC_ELT (cargs, i)))
> >   	      return type;
> 
> Let's factor this out; I imagine testing whether a concept-check is dependent
> (apart from the placeholder type itself) may be more broadly useful.

Done as well (into a predicate placeholder_type_constraint_dependent_p).
How does the following look? (testing in progress)

-- >8 --

gcc/cp/ChangeLog:

	PR c++/99365
	* pt.c (unify) <case TEMPLATE_TYPE_PARM>: Pass targs as
	outer_targs to do_auto_deduction.
	(placeholder_type_constraint_dependent_p): Define.
	(do_auto_deduction): When processing_template_decl != 0
	and context is adc_unify and we have constraints, pretend the
	constraints are satisfied instead of punting.  Otherwise don't
	punt unless placeholder_type_constraint_dependent_p holds.
	Add some clarifying sanity checks.  Add a hack to add missing
	outermost template levels to outer_args before checking
	satisfaction.  Don't substitute outer_targs into type if it's
	already been done.

gcc/testsuite/ChangeLog:

	PR c++/99365
	* g++.dg/cpp2a/concepts-partial-spec9.C: New test.
	* g++.dg/cpp2a/concepts-placeholder4: New test.
---
 gcc/cp/pt.c                                   | 57 +++++++++++++++++--
 .../g++.dg/cpp2a/concepts-partial-spec9.C     | 23 ++++++++
 .../g++.dg/cpp2a/concepts-placeholder4.C      | 24 ++++++++
 3 files changed, 98 insertions(+), 6 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-placeholder4.C

diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
index 8ca3dc8ec2b..b2ecd0b3213 100644
--- a/gcc/cp/pt.c
+++ b/gcc/cp/pt.c
@@ -23683,7 +23683,8 @@ unify (tree tparms, tree targs, tree parm, tree arg, int strict,
 
 	  if (tree a = type_uses_auto (tparm))
 	    {
-	      tparm = do_auto_deduction (tparm, arg, a, complain, adc_unify);
+	      tparm = do_auto_deduction (tparm, arg, a,
+					 complain, adc_unify, targs);
 	      if (tparm == error_mark_node)
 		return 1;
 	    }
@@ -28205,6 +28206,23 @@ make_constrained_decltype_auto (tree con, tree args)
   return make_constrained_placeholder_type (type, con, args);
 }
 
+/* Return true if the placeholder type constraint T has any dependent
+   (explicit) template arguments.  */
+
+static bool
+placeholder_type_constraint_dependent_p (tree t)
+{
+  tree id = unpack_concept_check (t);
+  tree args = TREE_OPERAND (id, 1);
+  tree first = TREE_VEC_ELT (args, 0);
+  gcc_checking_assert (TREE_CODE (first) == WILDCARD_DECL
+		       || is_auto (first));
+  for (int i = 1; i < TREE_VEC_LENGTH (args); ++i)
+    if (dependent_template_arg_p (TREE_VEC_ELT (args, i)))
+      return true;
+  return false;
+}
+
 /* Build and return a concept definition. Like other templates, the
    CONCEPT_DECL node is wrapped by a TEMPLATE_DECL.  This returns the
    the TEMPLATE_DECL. */
@@ -29613,13 +29631,20 @@ do_auto_deduction (tree type, tree init, tree auto_node,
     }
 
   /* Check any placeholder constraints against the deduced type. */
-  if (flag_concepts)
-    if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
+  if (processing_template_decl && context == adc_unify)
+    /* Constraints will be checked after deduction.  */;
+  else if (tree constr = NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
     {
       if (processing_template_decl)
-	  /* In general we can't check satisfaction until we know all
-	     template arguments.  */
+	{
+	  gcc_checking_assert (context == adc_variable_type
+			       || context == adc_return_type);
+	  gcc_checking_assert (!type_dependent_expression_p (init));
+	  /* If the constraint is dependent, we need to wait until
+	     instantiation time to resolve the placeholder.  */
+	  if (placeholder_type_constraint_dependent_p (constr))
 	    return type;
+	}
 
       if ((context == adc_return_type || context == adc_variable_type)
 	  && current_function_decl
@@ -29627,6 +29652,23 @@ do_auto_deduction (tree type, tree init, tree auto_node,
 	outer_targs = DECL_TI_ARGS (current_function_decl);
 
       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 (!constraints_satisfied_p (auto_node, full_targs))
 	{
 	  if (complain & tf_warning_or_error)
@@ -29658,7 +29700,10 @@ do_auto_deduction (tree type, tree init, tree auto_node,
 	}
     }
 
-  if (context == adc_unify)
+  if (TEMPLATE_TYPE_LEVEL (auto_node) == 1)
+    /* The outer template arguments are already substituted into type
+       (but we still may have used them for constraint checking above).  */;
+  else if (context == adc_unify)
     targs = add_to_template_args (outer_targs, targs);
   else if (processing_template_decl)
     targs = add_to_template_args (current_template_args (), targs);
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C b/gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
new file mode 100644
index 00000000000..3dae24d915a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
@@ -0,0 +1,23 @@
+// PR c++/99365
+// { dg-do compile { target c++20 } }
+
+template <class> concept C = true;
+template <class T, class U> concept D = C<T> && __is_same(T, U);
+
+template <class, C auto> struct A { static const int i = 0; };
+template <class T, D<T> auto V> struct A<T, V> { static const int i = 1; };
+
+static_assert(A<int, 0>::i == 1);
+static_assert(A<char, 0>::i == 0);
+static_assert(A<int, '0'>::i == 0);
+static_assert(A<char, '0'>::i == 1);
+
+template <class> struct O {
+  template <class, C auto> struct A { static const int i = 0; };
+  template <class T, D<T> auto V> struct A<T, V> { static const int i = 1; };
+};
+
+static_assert(O<void>::A<int, 0>::i == 1);
+static_assert(O<void>::A<char, 0>::i == 0);
+static_assert(O<void>::A<int, '0'>::i == 0);
+static_assert(O<void>::A<char, '0'>::i == 1);
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder4.C b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder4.C
new file mode 100644
index 00000000000..082c5d36cbe
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder4.C
@@ -0,0 +1,24 @@
+// { dg-do compile { target c++20 } }
+
+template <class T, class U> concept same_as = __is_same(T, U);
+
+// non-dependent initializer, always-satisfied constraint resolved at parse time
+template <class T> same_as<bool> auto x = true;
+
+template <auto V> same_as<int> auto y = V; // { dg-error "constraint" }
+
+template <class>
+struct A {
+  template <auto V> static inline same_as<int> auto z = V; // { dg-error "constraint" }
+};
+
+int main() {
+  x<int>;          // { dg-bogus "" }
+  y<0>;            // { dg-bogus "" }
+  y<'0'>;          // { dg-message "required from here" }
+  A<void>::z<0>;   // { dg-bogus "" }
+  A<void>::z<'0'>; // { dg-message "required from here" }
+}
+
+// non-dependent initializer, never-satisfied constraint diagnosed at parse time
+template <class T> same_as<int> auto z = true; // { dg-error "constraint" }
-- 
2.31.0.rc0.75.gec125d1bc1


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH] c++: adc_unify deduction with constrained auto [PR99365]
  2021-03-05 20:42             ` Patrick Palka
@ 2021-03-05 21:14               ` Jason Merrill
  0 siblings, 0 replies; 9+ messages in thread
From: Jason Merrill @ 2021-03-05 21:14 UTC (permalink / raw)
  To: Patrick Palka; +Cc: gcc-patches

On 3/5/21 3:42 PM, Patrick Palka wrote:
> On Fri, 5 Mar 2021, Jason Merrill wrote:
> 
>> On 3/4/21 9:55 PM, Patrick Palka wrote:
>>> On Thu, 4 Mar 2021, Patrick Palka wrote:
>>>
>>>> On Thu, 4 Mar 2021, Patrick Palka wrote:
>>>>
>>>>> On Thu, 4 Mar 2021, Jason Merrill wrote:
>>>>>
>>>>>> On 3/4/21 11:32 AM, Patrick Palka wrote:
>>>>>>> On Thu, 4 Mar 2021, Patrick Palka wrote:
>>>>>>>
>>>>>>>> My recent r11-7454 changed the way do_auto_deduction handles
>>>>>>>> constrained
>>>>>>>> placeholders during template argument deduction (context ==
>>>>>>>> adc_unify)
>>>>>>>> when processing_template_decl != 0.
>>>>>>>>
>>>>>>>> Before the patch, when processing_template_decl != 0 we would just
>>>>>>>> ignore the constraints on the placeholder in this situation, and
>>>>>>>> proceed
>>>>>>>> with deduction:
>>>>>>>>
>>>>>>>>      /* Check any placeholder constraints against the deduced type.
>>>>>>>> */
>>>>>>>>      if (flag_concepts && !processing_template_decl)
>>>>>>>>        if (tree check = NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS
>>>>>>>> (auto_node)))
>>>>>>>>          {
>>>>>>>>            ...
>>>>>>>>
>>>>>>>> After the patch, we now punt and return the original placeholder
>>>>>>>> type:
>>>>>>>>
>>>>>>>>      /* Check any placeholder constraints against the deduced type.
>>>>>>>> */
>>>>>>>>      if (flag_concepts)
>>>>>>>>        if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
>>>>>>>>          {
>>>>>>>>            if (processing_template_decl)
>>>>>>>>              /* In general we can't check satisfaction until we
>>>>>>>> know all
>>>>>>>>                 template arguments.  */
>>>>>>>>              return type;
>>>>>>>>            ...
>>>>>>>>
>>>>>>>> While this change fixed instances where we'd prematurely resolve a
>>>>>>>> constrained placeholder return or variable type with non-dependent
>>>>>>>> initializer at template parse time (such as PR96444), it broke the
>>>>>>>> adc_unify callers that rely on this previous behavior.
>>>>>>>>
>>>>>>>> So this patch restores the previous behavior during adc_unify
>>>>>>>> deduction
>>>>>>>> while retaining the new behavior only during adc_variable_type or
>>>>>>>> adc_return_type deduction.
>>>>>>
>>>>>> Sure, it makes sense for adc_unify to behave differently, since in
>>>>>> deduction
>>>>>> context constraints are checked after all template args have been
>>>>>> deduced.
>>>>>
>>>>> I see.
>>>>>
>>>>>>
>>>>>>>> We additionally now need to pass outer template arguments to
>>>>>>>> do_auto_deduction during unify, for sake of constraint checking.
>>>>>>>> But we don't want do_auto_deduction to substitute these outer
>>>>>>>> arguments
>>>>>>>> into type if it's already been done, hence the added
>>>>>>>> TEMPLATE_TYPE_LEVEL
>>>>>>>> check.
>>>>>>>>
>>>>>>>> This fixes partial specializations of non-nested templates with
>>>>>>>> constrained 'auto' template parameters, but nested templates are
>>>>>>>> still
>>>>>>>> broken, ultimately because most_specialized_partial_spec passes
>>>>>>>> only the
>>>>>>>> innermost template arguments to get_partial_spec_bindings, and so
>>>>>>>> outer_targs during do_auto_deduction (called from unify) contains
>>>>>>>> only
>>>>>>>> the innermost template arguments which makes satisfaction unhappy.
>>>>>>>> Fixing this might be too invasive at this stage, perhaps..  (Seems
>>>>>>>> we
>>>>>>>> need to make most_specialized_partial_spec pass all template
>>>>>>>> arguments
>>>>>>>> to get_partial_spec_bindings.)
>>>>>>
>>>>>> How did this work before?
>>>>>
>>>>> Before, it would work, but only if the constraint didn't also depend on
>>>>> any outer template arguments.  do_auto_deduction would just "surgically"
>>>>> replace the auto in the concept-id with the type we deduced and leave
>>>>> the other template arguments of the concept-id alone.  So if the
>>>>> constraint was non-dependent, satisfaction would work regardless of the
>>>>> template nesting level.
>>>>>
>>>>> Now that we try to do perform satisfaction properly, we're sensitive to
>>>>> the template nesting level even if the constraint is otherwise
>>>>> non-dependent, because the template nesting level determines the level
>>>>> of the auto that appears inside the constraint.  So we rely on
>>>>> outer_targs to contain all levels of outer template arguments, because
>>>>> we tack on another level to the end of outer_targs which needs to
>>>>> map to the level of the auto for satisfaction.
>>>>>
>>>>> (As a hacky workaround, when outer_targs is incomplete, can probably
>>>>> just augment it with empty levels until it's
>>>>> TEMPLATE_TYPE_LEVEL(auto_node)-1
>>>>> levels deep, which would fix the nested template case as long as the
>>>>> constraint was depended only on the innermost level of template
>>>>> arguments.)
>>>>>
>>>>>>
>>>>>>>> Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look
>>>>>>>> OK for
>>>>>>>> trunk?  Also tested on range-v3 and cmcstl2.
>>>>>>>
>>>>>>> Here's the same patch generated with -w which hides the noisy
>>>>>>> indentation
>>>>>>> changes:
>>>>>>>
>>>>>>> -- >8 --
>>>>>>>
>>>>>>> 	PR c++/99365
>>>>>>> 	* pt.c (do_auto_deduction): When processing_template_decl != 0
>>>>>>> 	and context is adc_unify and we have constraints, pretend the
>>>>>>> 	constraints are satisfied instead of punting.  Add some
>>>>>>> 	clarifying sanity checks.  Don't substitute outer_targs into
>>>>>>> 	type if not needed.
>>>>>>>
>>>>>>> gcc/testsuite/ChangeLog:
>>>>>>>
>>>>>>> 	PR c++/99365
>>>>>>> 	* g++.dg/cpp2a/concepts-partial-spec9.C: New test.
>>>>>>> ---
>>>>>>>     gcc/cp/pt.c                                   | 24
>>>>>>> ++++++++++++++-----
>>>>>>>     .../g++.dg/cpp2a/concepts-partial-spec9.C     | 24
>>>>>>> +++++++++++++++++++
>>>>>>>     2 files changed, 42 insertions(+), 6 deletions(-)
>>>>>>>     create mode 100644
>>>>>>> gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
>>>>>>>
>>>>>>> diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
>>>>>>> index a4686e0affb..ce537e4529a 100644
>>>>>>> --- a/gcc/cp/pt.c
>>>>>>> +++ b/gcc/cp/pt.c
>>>>>>> @@ -23693,7 +23693,8 @@ unify (tree tparms, tree targs, tree parm,
>>>>>>> tree arg,
>>>>>>> int strict,
>>>>>>>       	  if (tree a = type_uses_auto (tparm))
>>>>>>>     	    {
>>>>>>> -	      tparm = do_auto_deduction (tparm, arg, a, complain,
>>>>>>> adc_unify);
>>>>>>> +	      tparm = do_auto_deduction (tparm, arg, a,
>>>>>>> +					 complain, adc_unify, targs);
>>>>>>>     	      if (tparm == error_mark_node)
>>>>>>>     		return 1;
>>>>>>>     	    }
>>>>>>> @@ -29619,13 +29620,21 @@ do_auto_deduction (tree type, tree init,
>>>>>>> tree
>>>>>>> auto_node,
>>>>>>>         }
>>>>>>>         /* Check any placeholder constraints against the deduced
>>>>>>> type. */
>>>>>>> -  if (flag_concepts)
>>>>>>> -    if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
>>>>>>> +  if (processing_template_decl && context == adc_unify)
>>>>>>> +    /* Pretend constraints are satisfied.  */;
>>>>>>> +  else if (flag_concepts
>>>>>>> +	   && NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
>>>>>>>         {
>>>>>>>           if (processing_template_decl)
>>>>>>> -	  /* In general we can't check satisfaction until we know all
>>>>>>> -	     template arguments.  */
>>>>>>> +	{
>>>>>>> +	  /* Even though the initializer is non-dependent, we need to
>>>>>>> wait
>>>>>>> until
>>>>>>> +	     instantiation time to resolve this constrained
>>>>>>> placeholder
>>>>>>> variable
>>>>>>> +	     or return type, since the constraint itself may be
>>>>>>> dependent.  */
>>>>>>
>>>>>> Can't we check whether the constraint is dependent, and check
>>>>>> satisfaction if
>>>>>> it isn't?  That might be necessary to make later expressions
>>>>>> non-dependent
>>>>>> that are supposed to be.
>>>>>
>>>>> We'd have to check if outer_targs (and the deduced type) is dependent
>>>>> too.  But it seems tricky because outer_targs, during adc_unify
>>>>> deduction, is usually at least partially empty which is enough to make
>>>>> any_dependent_template_arguments_p return true.
>>>>
>>>> Ah sorry, I just realized you probably meant we should check whether the
>>>> constraint is dependent during adc_variable_type and adc_return_type
>>>> deduction.  I think that might be straightforward; I'll try it.  I'll
>>>> also experiment with the hacky workaround mentioned earlier.
>>>
>>> Here's a patch that incorporates these two ideas:
>>>
>>> -- >8 --
>>>
>>> Subject: [PATCH] c++: adc_unify deduction with constrained auto [PR99365]
>>>
>>> My recent r11-7454 changed the way do_auto_deduction handles constrained
>>> placeholders during template argument deduction (context == adc_unify)
>>> when processing_template_decl != 0.
>>>
>>> Before the patch, when processing_template_decl != 0 we would just
>>> ignore the constraints on the placeholder in this situation, and proceed
>>> with deduction:
>>>
>>>     /* Check any placeholder constraints against the deduced type. */
>>>     if (flag_concepts && !processing_template_decl)
>>>       if (tree check = NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
>>>         {
>>>           ...
>>>
>>> After the patch, we now punt and return the original placeholder type:
>>>
>>>     /* Check any placeholder constraints against the deduced type. */
>>>     if (flag_concepts)
>>>       if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
>>>         {
>>>           if (processing_template_decl)
>>>             /* In general we can't check satisfaction until we know all
>>>                template arguments.  */
>>>             return type;
>>>           ...
>>>
>>> While this change fixed instances where we'd prematurely resolve a
>>> constrained placeholder return or variable type with non-dependent
>>> initializer at template parse time (such as PR96444), it broke the
>>> adc_unify callers that rely on the previous behavior.
>>>
>>> So this patch restores the previous behavior during adc_unify deduction,
>>> while retaining the new behavior only during adc_variable_type or
>>> adc_return_type deduction.
>>>
>>> We additionally now need to pass the outer template arguments to
>>> do_auto_deduction during unify, for sake of constraint checking.
>>> But we don't want do_auto_deduction to substitute these outer arguments
>>> into type when the caller has already done so, so this patch adds
>>> a TEMPLATE_TYPE_LEVEL check to do_auto_deduction to that effect.
>>>
>>> Overall, these changes fix partial specialization of non-nested
>>> templates with constrained 'auto' template parameters, but nested
>>> templates are still broken, ultimately because
>>> most_specialized_partial_spec passes only the innermost template
>>> arguments to get_partial_spec_bindings, and so outer_targs during
>>> do_auto_deduction (called from unify) contains only the innermost
>>> template arguments which makes satisfaction unhappy.  Fixing this
>>> properly is perhaps too risky at this stage, so this patch adds a hack
>>> to do_auto_deduction to compensate for callers that don't supply all
>>> outer template arguments.  The goal of this hack is to ensure
>>> placeholder type constraint checking continues to work whenever it
>>> worked before r11-7454, namely whenever the constraint is non-dependent.
>>>
>>> Finally, this patch allows do_auto_deduction to resolve a constrained
>>> placeholder type ahead of time (at template parse time), as long as the
>>> constraint is non-dependent.
>>>
>>> Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for
>>> trunk?  Also tested on range-v3 and cmcstl2.
>>>
>>> gcc/cp/ChangeLog:
>>>
>>> 	PR c++/99365
>>> 	* pt.c (do_auto_deduction): When processing_template_decl != 0
>>> 	and context is adc_unify and we have constraints, pretend the
>>> 	constraints are satisfied instead of punting.  Otherwise don't
>>> 	punt unless any of the explicit arguments in the constraint are
>>> 	dependent.  Add some clarifying sanity checks.  Add a hack to
>>> 	add missing outermost template levels to outer_args before
>>> 	checking satisfaction.  Don't substitute outer_targs into type
>>> 	if it's already been done.
>>>
>>> gcc/testsuite/ChangeLog:
>>>
>>> 	PR c++/99365
>>> 	* g++.dg/cpp2a/concepts-partial-spec9.C: New test.
>>> 	* g++.dg/cpp2a/concepts-placeholder4: New test.
>>> ---
>>>    gcc/cp/pt.c                                   | 44 ++++++++++++++++---
>>>    .../g++.dg/cpp2a/concepts-partial-spec9.C     | 23 ++++++++++
>>>    .../g++.dg/cpp2a/concepts-placeholder4.C      | 24 ++++++++++
>>>    3 files changed, 85 insertions(+), 6 deletions(-)
>>>    create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
>>>    create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-placeholder4.C
>>>
>>> diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
>>> index 83589101c0d..31386a74a59 100644
>>> --- a/gcc/cp/pt.c
>>> +++ b/gcc/cp/pt.c
>>> @@ -23681,7 +23681,8 @@ unify (tree tparms, tree targs, tree parm, tree arg,
>>> int strict,
>>>      	  if (tree a = type_uses_auto (tparm))
>>>    	    {
>>> -	      tparm = do_auto_deduction (tparm, arg, a, complain, adc_unify);
>>> +	      tparm = do_auto_deduction (tparm, arg, a,
>>> +					 complain, adc_unify, targs);
>>>    	      if (tparm == error_mark_node)
>>>    		return 1;
>>>    	    }
>>> @@ -29611,13 +29612,24 @@ do_auto_deduction (tree type, tree init, tree
>>> auto_node,
>>>        }
>>>        /* Check any placeholder constraints against the deduced type. */
>>> -  if (flag_concepts)
>>> -    if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
>>> +  if (processing_template_decl && context == adc_unify)
>>> +    /* Pretend constraints are satisfied.  */;
>>
>> Let's say "constraints will be checked after deduction".
> 
> Done.
> 
>>
>>> +  else if (tree constr = NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS
>>> (auto_node)))
>>>        {
>>>          if (processing_template_decl)
>>> -	  /* In general we can't check satisfaction until we know all
>>> -	     template arguments.  */
>>> +	{
>>> +	  gcc_checking_assert (context == adc_variable_type
>>> +			       || context == adc_return_type);
>>> +	  gcc_checking_assert (!type_dependent_expression_p (init));
>>
>> This assert seems redundant with the condition at the top of the function,
>> though I suppose it's harmless to have it here as well.
> 
> Yeah, I figured the assert might be beneficial to the reader, as a
> reminder that we're dealing with a non-dependent initializer, since the
> condition is a bit far away at this point.
> 
>>
>>> +	  /* Check if any of the explicit arguments in the constraint
>>> +	     are dependent.  If so, we need to wait until instantiation time
>>> +	     to resolve the constriant.  */
>>> +	  tree cid = unpack_concept_check (constr);
>>> +	  tree cargs = TREE_OPERAND (cid, 1);
>>> +	  for (int i = 1; i < TREE_VEC_LENGTH (cargs); ++i)
>>> +	    if (dependent_template_arg_p (TREE_VEC_ELT (cargs, i)))
>>>    	      return type;
>>
>> Let's factor this out; I imagine testing whether a concept-check is dependent
>> (apart from the placeholder type itself) may be more broadly useful.
> 
> Done as well (into a predicate placeholder_type_constraint_dependent_p).
> How does the following look? (testing in progress)

OK, thanks.

> -- >8 --
> 
> gcc/cp/ChangeLog:
> 
> 	PR c++/99365
> 	* pt.c (unify) <case TEMPLATE_TYPE_PARM>: Pass targs as
> 	outer_targs to do_auto_deduction.
> 	(placeholder_type_constraint_dependent_p): Define.
> 	(do_auto_deduction): When processing_template_decl != 0
> 	and context is adc_unify and we have constraints, pretend the
> 	constraints are satisfied instead of punting.  Otherwise don't
> 	punt unless placeholder_type_constraint_dependent_p holds.
> 	Add some clarifying sanity checks.  Add a hack to add missing
> 	outermost template levels to outer_args before checking
> 	satisfaction.  Don't substitute outer_targs into type if it's
> 	already been done.
> 
> gcc/testsuite/ChangeLog:
> 
> 	PR c++/99365
> 	* g++.dg/cpp2a/concepts-partial-spec9.C: New test.
> 	* g++.dg/cpp2a/concepts-placeholder4: New test.
> ---
>   gcc/cp/pt.c                                   | 57 +++++++++++++++++--
>   .../g++.dg/cpp2a/concepts-partial-spec9.C     | 23 ++++++++
>   .../g++.dg/cpp2a/concepts-placeholder4.C      | 24 ++++++++
>   3 files changed, 98 insertions(+), 6 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-placeholder4.C
> 
> diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
> index 8ca3dc8ec2b..b2ecd0b3213 100644
> --- a/gcc/cp/pt.c
> +++ b/gcc/cp/pt.c
> @@ -23683,7 +23683,8 @@ unify (tree tparms, tree targs, tree parm, tree arg, int strict,
>   
>   	  if (tree a = type_uses_auto (tparm))
>   	    {
> -	      tparm = do_auto_deduction (tparm, arg, a, complain, adc_unify);
> +	      tparm = do_auto_deduction (tparm, arg, a,
> +					 complain, adc_unify, targs);
>   	      if (tparm == error_mark_node)
>   		return 1;
>   	    }
> @@ -28205,6 +28206,23 @@ make_constrained_decltype_auto (tree con, tree args)
>     return make_constrained_placeholder_type (type, con, args);
>   }
>   
> +/* Return true if the placeholder type constraint T has any dependent
> +   (explicit) template arguments.  */
> +
> +static bool
> +placeholder_type_constraint_dependent_p (tree t)
> +{
> +  tree id = unpack_concept_check (t);
> +  tree args = TREE_OPERAND (id, 1);
> +  tree first = TREE_VEC_ELT (args, 0);
> +  gcc_checking_assert (TREE_CODE (first) == WILDCARD_DECL
> +		       || is_auto (first));
> +  for (int i = 1; i < TREE_VEC_LENGTH (args); ++i)
> +    if (dependent_template_arg_p (TREE_VEC_ELT (args, i)))
> +      return true;
> +  return false;
> +}
> +
>   /* Build and return a concept definition. Like other templates, the
>      CONCEPT_DECL node is wrapped by a TEMPLATE_DECL.  This returns the
>      the TEMPLATE_DECL. */
> @@ -29613,13 +29631,20 @@ do_auto_deduction (tree type, tree init, tree auto_node,
>       }
>   
>     /* Check any placeholder constraints against the deduced type. */
> -  if (flag_concepts)
> -    if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
> +  if (processing_template_decl && context == adc_unify)
> +    /* Constraints will be checked after deduction.  */;
> +  else if (tree constr = NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
>       {
>         if (processing_template_decl)
> -	  /* In general we can't check satisfaction until we know all
> -	     template arguments.  */
> +	{
> +	  gcc_checking_assert (context == adc_variable_type
> +			       || context == adc_return_type);
> +	  gcc_checking_assert (!type_dependent_expression_p (init));
> +	  /* If the constraint is dependent, we need to wait until
> +	     instantiation time to resolve the placeholder.  */
> +	  if (placeholder_type_constraint_dependent_p (constr))
>   	    return type;
> +	}
>   
>         if ((context == adc_return_type || context == adc_variable_type)
>   	  && current_function_decl
> @@ -29627,6 +29652,23 @@ do_auto_deduction (tree type, tree init, tree auto_node,
>   	outer_targs = DECL_TI_ARGS (current_function_decl);
>   
>         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 (!constraints_satisfied_p (auto_node, full_targs))
>   	{
>   	  if (complain & tf_warning_or_error)
> @@ -29658,7 +29700,10 @@ do_auto_deduction (tree type, tree init, tree auto_node,
>   	}
>       }
>   
> -  if (context == adc_unify)
> +  if (TEMPLATE_TYPE_LEVEL (auto_node) == 1)
> +    /* The outer template arguments are already substituted into type
> +       (but we still may have used them for constraint checking above).  */;
> +  else if (context == adc_unify)
>       targs = add_to_template_args (outer_targs, targs);
>     else if (processing_template_decl)
>       targs = add_to_template_args (current_template_args (), targs);
> diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C b/gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
> new file mode 100644
> index 00000000000..3dae24d915a
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
> @@ -0,0 +1,23 @@
> +// PR c++/99365
> +// { dg-do compile { target c++20 } }
> +
> +template <class> concept C = true;
> +template <class T, class U> concept D = C<T> && __is_same(T, U);
> +
> +template <class, C auto> struct A { static const int i = 0; };
> +template <class T, D<T> auto V> struct A<T, V> { static const int i = 1; };
> +
> +static_assert(A<int, 0>::i == 1);
> +static_assert(A<char, 0>::i == 0);
> +static_assert(A<int, '0'>::i == 0);
> +static_assert(A<char, '0'>::i == 1);
> +
> +template <class> struct O {
> +  template <class, C auto> struct A { static const int i = 0; };
> +  template <class T, D<T> auto V> struct A<T, V> { static const int i = 1; };
> +};
> +
> +static_assert(O<void>::A<int, 0>::i == 1);
> +static_assert(O<void>::A<char, 0>::i == 0);
> +static_assert(O<void>::A<int, '0'>::i == 0);
> +static_assert(O<void>::A<char, '0'>::i == 1);
> diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder4.C b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder4.C
> new file mode 100644
> index 00000000000..082c5d36cbe
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder4.C
> @@ -0,0 +1,24 @@
> +// { dg-do compile { target c++20 } }
> +
> +template <class T, class U> concept same_as = __is_same(T, U);
> +
> +// non-dependent initializer, always-satisfied constraint resolved at parse time
> +template <class T> same_as<bool> auto x = true;
> +
> +template <auto V> same_as<int> auto y = V; // { dg-error "constraint" }
> +
> +template <class>
> +struct A {
> +  template <auto V> static inline same_as<int> auto z = V; // { dg-error "constraint" }
> +};
> +
> +int main() {
> +  x<int>;          // { dg-bogus "" }
> +  y<0>;            // { dg-bogus "" }
> +  y<'0'>;          // { dg-message "required from here" }
> +  A<void>::z<0>;   // { dg-bogus "" }
> +  A<void>::z<'0'>; // { dg-message "required from here" }
> +}
> +
> +// non-dependent initializer, never-satisfied constraint diagnosed at parse time
> +template <class T> same_as<int> auto z = true; // { dg-error "constraint" }
> 


^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2021-03-05 21:15 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-03-04 16:26 [PATCH] c++: adc_unify deduction with constrained auto [PR99365] Patrick Palka
2021-03-04 16:32 ` Patrick Palka
2021-03-04 21:03   ` Jason Merrill
2021-03-04 21:45     ` Patrick Palka
2021-03-04 21:51       ` Patrick Palka
2021-03-05  2:55         ` Patrick Palka
2021-03-05 19:12           ` Jason Merrill
2021-03-05 20:42             ` Patrick Palka
2021-03-05 21:14               ` Jason Merrill

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).