Thanks for the patch!

On Wed, 1 May 2024, Seyed Sajad Kahani wrote:

> When deducing auto for `adc_return_type`, `adc_variable_type`, and 
> `adc_decomp_type` contexts (at the usage time), we try to resolve the 
> outermost template arguments to be used for satisfaction. This is done by one 
> of the following, depending on the scope:
> 
> 1. Checking the `DECL_TEMPLATE_INFO` of the current function scope and 
> extracting DECL_TI_ARGS from it for function scope deductions (pt.cc:31236).
> 2. Checking the `DECL_TEMPLATE_INFO` of the declaration (alongside with other 
> conditions) for non-function scope variable declaration deductions 
> (decl.cc:8527).
> 
> Then, we do not retrieve the deeper layers of the template arguments; 
> instead, we fill the missing levels with dummy levels (pt.cc:31260).
> 
> The problem (that is shown in PR114915) is that we do not consider the case 
> where the deduction happens in a template specialization scope. In this case, 
> the type is not dependent on the outermost template arguments (which are the 
> specialization arguments). Yet, we still resolve the outermost template 
> arguments, and then the number of layers in the template arguments exceeds 
> the number of levels in the type. This causes the missing levels to be 
> negative. This leads to the rejection of valid code and ICEs (like segfault) 
> in the release mode. In the debug mode, it is possible to show as an 
> assertion failure (when creating a tree_vec with a negative size).
> The code that generates the issue is added to the test suite as 
> `g++.dg/cpp2a/concepts-placeholder14.C`.
> 
> This patch fixes the issue by checking that the template usage, whose 
> arguments are going to be used for satisfaction, is not a partial or explicit 
> specialization (and therefore it is an implicit or explicit instantiation). 
> This check is done in the two only places that affect the `outer_targs` for 
> the mentioned contexts.
> 
> One might ask why this is not implemented as a simple `missing_level > 0` 
> check. The reason is that the recovery from the negative `missing_levels` 
> will not be easy, and it is not clear how to recover from it. Therefore, it 
> is better to prevent it from happening.
> ---
>  gcc/cp/decl.cc                                |  1 +
>  gcc/cp/pt.cc                                  | 16 ++++++++++-----
>  .../g++.dg/cpp2a/concepts-placeholder14.C     | 20 +++++++++++++++++++
>  3 files changed, 32 insertions(+), 5 deletions(-)
>  create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-placeholder14.C
> 
> diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
> index 65ab64885..7e51f926e 100644
> --- a/gcc/cp/decl.cc
> +++ b/gcc/cp/decl.cc
> @@ -8527,6 +8527,7 @@ cp_finish_decl (tree decl, tree init, bool 
> init_const_expr_p,
>        if (PLACEHOLDER_TYPE_CONSTRAINTS_INFO (auto_node)
>         && DECL_LANG_SPECIFIC (decl)
>         && DECL_TEMPLATE_INFO (decl)
> +       && DECL_USE_TEMPLATE (decl) != 2

We should check !DECL_TEMPLATE_SPECIALIZATION instead of checking
DECL_USE_TEMPLATE directly.

And since DECL_TEMPLATE_SPECIALIZATION is true for both partial and
explicit specializations, I think we should check !in_template_context
as well in order to single out explicit specializations?

>         && !DECL_FUNCTION_SCOPE_P (decl))
>       /* The outer template arguments might be needed for satisfaction.
>          (For function scope variables, do_auto_deduction will obtain the
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index 3b2106dd3..fd646d873 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -31044,7 +31044,8 @@ unparenthesized_id_or_class_member_access_p (tree 
> init)
>     OUTER_TARGS is used during template argument deduction (context == 
> adc_unify)
>     to properly substitute the result.  It's also used in the adc_unify and
>     adc_requirement contexts to communicate the necessary template arguments
> -   to satisfaction.  OUTER_TARGS is ignored in other contexts.
> +   to satisfaction.  OUTER_TARGS will be used for other contexts if it is a
> +   function scope deduction. Otherwise it is ignored.
>  
>     Additionally for adc_unify contexts TMPL is the template for which TYPE
>     is a template parameter type.
> @@ -31235,8 +31236,11 @@ do_auto_deduction (tree type, tree init, tree 
> auto_node,
>       if (tree fn = current_function_decl)
>         if (DECL_TEMPLATE_INFO (fn) || LAMBDA_FUNCTION_P (fn))
>           {
> -           outer_targs = DECL_TEMPLATE_INFO (fn)
> -             ? DECL_TI_ARGS (fn) : NULL_TREE;
> +           outer_targs = NULL_TREE; 
> +           if (DECL_TEMPLATE_INFO (fn) && DECL_USE_TEMPLATE (fn) != 2)
> +           {
> +               outer_targs = DECL_TI_ARGS (fn);
> +           }

Same here.

>             if (LAMBDA_FUNCTION_P (fn))
>               {
>                 /* As in satisfy_declaration_constraints.  */
> @@ -31260,8 +31264,10 @@ do_auto_deduction (tree type, tree init, tree 
> auto_node,
>        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)))
> +      int missing_levels = (TEMPLATE_TYPE_ORIG_LEVEL (auto_node)
> +                                 - TMPL_ARGS_DEPTH (full_targs));
> +
> +      if (missing_levels > 0)

IIUC this change is not necessary with the above changes, right?  Maybe
we could assert than missing_levels is nonnegative.

Another more context-unaware approach to fix this might be to only
use the innermost level from 'full_targs' for satisfaction if
TEMPLATE_TYPE_ORIG_LEVEL is 1 (which means this constrained auto
appeared in a context that doesn't have template parameters such as an
explicit specialization or ordinary non-template function, and so
only the level corresponding to the deduced type is needed for
satisfaction.)

Generalizing on that, another approach might be to handle missing_levels < 0
by removing -missing_levels from full_targs via get_innermost_template_args.
But AFAICT it should suffice to handle the TEMPLATE_TYPE_ORIG_LEVEL == 1
case specifically.

I wonder what Jason thinks?

>       {
>         tree dummy_levels = make_tree_vec (missing_levels);
>         for (int i = 0; i < missing_levels; ++i)
> diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder14.C 
> b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder14.C
> new file mode 100644
> index 000000000..4a98ec7b3
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder14.C
> @@ -0,0 +1,20 @@
> +// PR c++/114915
> +// { dg-do compile { target c++20 } }
> +
> +template<typename T>
> +concept TheConcept = __is_same(T, int);
> +
> +template<typename T>
> +void f() {
> +  TheConcept auto x = 1;
> +}
> +
> +template<>
> +void f<int>() {
> +  TheConcept auto x = 1;
> +}
> +
> +int main() {
> +  f<int>();
> +  return 0;
> +}

It would be good to also test an explicit variable tmpl spec and
an explicit spec of a member template of a class template.

> -- 
> 2.44.0
> 
> 

Reply via email to