[v2] c++: ICE with auto(0) in requires-expression [PR103408]
Commit Message
On Wed, Dec 01, 2021 at 12:17:47PM -0500, Patrick Palka wrote:
> On Wed, Dec 1, 2021 at 10:26 AM Marek Polacek via Gcc-patches
> <gcc-patches@gcc.gnu.org> wrote:
> >
> > Here we ICE on
> >
> > int f() requires (auto(0));
> >
> > in do_auto_deduction when handling the auto: we're in a non-templated
> > requires-expression which are parsed under processing_template_decl == 1
> > and empty current_template_parms, so 'current_template_args ()' will
> > crash. This code is invalid as per "C++20 CA378: Remove non-templated
> > constrained functions", but of course we shouldn't crash.
>
> FWIW it looks like we can trip over the same bug with valid code:
>
> static_assert(requires { auto(0); });
Right, I hadn't thought of this at all. So it's not just an ICE-on-invalid!
> > Since in the scenario above it's expected that current_template_parms is
> > null, I've just added a check, and let grokfndecl issue an error.
> > I guess another approch would be to fake up a template parameter list
> > before calling do_auto_deduction.
> >
> > For good measure, I've added several well-formed cases with auto(x) in
> > a requires-expression.
> >
> > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
> >
> > PR c++/103408
> >
> > gcc/cp/ChangeLog:
> >
> > * pt.c (do_auto_deduction): Check current_template_parms before
> > current_template_args ().
> >
> > gcc/testsuite/ChangeLog:
> >
> > * g++.dg/cpp23/auto-fncast9.C: New test.
> > ---
> > gcc/cp/pt.c | 2 +-
> > gcc/testsuite/g++.dg/cpp23/auto-fncast9.C | 27 +++++++++++++++++++++++
> > 2 files changed, 28 insertions(+), 1 deletion(-)
> > create mode 100644 gcc/testsuite/g++.dg/cpp23/auto-fncast9.C
> >
> > diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
> > index f4b9d9673fb..012ca5d06c0 100644
> > --- a/gcc/cp/pt.c
> > +++ b/gcc/cp/pt.c
> > @@ -30041,7 +30041,7 @@ do_auto_deduction (tree type, tree init, tree auto_node,
> > (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)
> > + else if (processing_template_decl && current_template_parms)
> > targs = add_to_template_args (current_template_args (), targs);
> > return tsubst (type, targs, complain, NULL_TREE);
>
> Won't this mean the call to tsubst here will end up lowering the level
> of the auto from 2 to 1 rather than replacing it with the actual
> deduced type?
Yes, exactly -- targs has depth 1 but the level of auto is 2. :(
> It also looks like this approach doesn't handle static_assert(requires
> { auto(auto(0)); }), probably due to this substitution issue. I guess
> we could add a dummy level to 'targs' to work around this..
Thanks for pointing this out. Here's a v2 which uses a dummy level. The
description hopefully explains what's going on here...
Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
-- >8 --
Here we ICE on
int f() requires (auto(0));
in do_auto_deduction when handling the auto: we're in a non-templated
requires-expression which are parsed under processing_template_decl == 1
and empty current_template_parms, so 'current_template_args ()' will
crash. This code is invalid as per "C++20 CA378: Remove non-templated
constrained functions", but of course we shouldn't crash. However, as
Patrick pointed out, this is valid:
static_assert(requires { auto(0); }); // #1
static_assert(requires { auto(auto(0)); }); // #2
To make #1 and #2 work, I'm playing games with faking up a dummy template
parameter level to current_template_parms, which need elaborating a bit.
autos are created with the level processing_template_decl + 1. When we
call do_auto_deduction for the inner auto(0) in #2, the auto will therefore
have level 2. type_unification_real in do_auto_deduction deduces targs to
<int> (TMPL_ARGS_DEPTH = 1), so the
return tsubst (type, targs, complain, NULL_TREE);
will only reduce the level of the auto to 1. So it remains undeduced,
causing the "invalid use of auto" error. If I create the dummy template
parameter level, then this at the end of do_auto_deduction
else if (processing_template_decl)
targs = add_to_template_args (current_template_args (), targs);
transforms <int> into <, int>, which has TMPL_ARGS_DEPTH = 2 and so the
following tsubst actually replaces the auto with int, and things work.
For good measure, I've added several well-formed cases with auto(x) in
a requires-expression.
PR c++/103408
gcc/cp/ChangeLog:
* cp-tree.h (add_dummy_template_parameter_level): Declare.
* pt.c (add_dummy_template_parameter_level): New function.
(begin_template_parm_list): Use it.
* semantics.c (finish_compound_literal): Call
add_dummy_template_parameter_level if there are no template parameters.
* typeck2.c (build_functional_cast_1): Likewise.
gcc/testsuite/ChangeLog:
* g++.dg/cpp23/auto-fncast9.C: New test.
---
gcc/cp/cp-tree.h | 1 +
gcc/cp/pt.c | 15 +++++++---
gcc/cp/semantics.c | 3 ++
gcc/cp/typeck2.c | 3 ++
gcc/testsuite/g++.dg/cpp23/auto-fncast9.C | 35 +++++++++++++++++++++++
5 files changed, 53 insertions(+), 4 deletions(-)
create mode 100644 gcc/testsuite/g++.dg/cpp23/auto-fncast9.C
base-commit: 860c56b5bc356960a4d0445dadc43ceddbe3c7e2
@@ -7174,6 +7174,7 @@ extern tree get_innermost_template_args (tree, int);
extern void maybe_begin_member_template_processing (tree);
extern void maybe_end_member_template_processing (void);
extern tree finish_member_template_decl (tree);
+extern void add_dummy_template_parameter_level ();
extern void begin_template_parm_list (void);
extern bool begin_specialization (void);
extern void reset_specialization (void);
@@ -689,6 +689,16 @@ strip_innermost_template_args (tree args, int extra_levels)
return new_args;
}
+/* Add a dummy template parameter level to current_template_parms. */
+
+void
+add_dummy_template_parameter_level ()
+{
+ current_template_parms = tree_cons (size_int (processing_template_decl),
+ make_tree_vec (0),
+ current_template_parms);
+}
+
/* We've got a template header coming up; push to a new level for storing
the parms. */
@@ -715,10 +725,7 @@ begin_template_parm_list (void)
note_template_header (0);
/* Add a dummy parameter level while we process the parameter list. */
- current_template_parms
- = tree_cons (size_int (processing_template_decl),
- make_tree_vec (0),
- current_template_parms);
+ add_dummy_template_parameter_level ();
}
/* This routine is called when a specialization is declared. If it is
@@ -3143,6 +3143,9 @@ finish_compound_literal (tree type, tree compound_literal,
pedwarn (input_location, OPT_Wc__23_extensions,
"%<auto{x}%> only available with "
"%<-std=c++2b%> or %<-std=gnu++2b%>");
+ auto ctp = make_temp_override (current_template_parms);
+ if (processing_template_decl && !current_template_parms)
+ add_dummy_template_parameter_level ();
type = do_auto_deduction (type, compound_literal, type, complain,
adc_variable_type);
if (type == error_mark_node)
@@ -2202,6 +2202,7 @@ build_functional_cast_1 (location_t loc, tree exp, tree parms,
if (tree anode = type_uses_auto (type))
{
tree init;
+ auto ctp = make_temp_override (current_template_parms);
if (CLASS_PLACEHOLDER_TEMPLATE (anode))
init = parms;
/* C++23 auto(x). */
@@ -2213,6 +2214,8 @@ build_functional_cast_1 (location_t loc, tree exp, tree parms,
pedwarn (loc, OPT_Wc__23_extensions,
"%<auto(x)%> only available with "
"%<-std=c++2b%> or %<-std=gnu++2b%>");
+ if (processing_template_decl && !current_template_parms)
+ add_dummy_template_parameter_level ();
}
else
{
new file mode 100644
@@ -0,0 +1,35 @@
+// PR c++/103408
+// { dg-do compile { target c++23 } }
+
+int bad1() requires (auto(true)); // { dg-error "constraints on a non-templated function" }
+
+template<bool B>
+int f1() requires (auto(B));
+
+template<typename T>
+struct S { T t; constexpr operator bool() { return true; } };
+
+int bad2() requires (bool(S{1})); // { dg-error "constraints on a non-templated function" }
+int bad3() requires (bool(S(1))); // { dg-error "constraints on a non-templated function" }
+
+template<int N>
+int f2() requires (bool(S{N}));
+
+template<int N>
+int f3() requires (bool(S(N)));
+
+void
+g ()
+{
+ f1<true>();
+ f2<42>();
+ f3<42>();
+}
+
+static_assert(requires { auto(0); });
+static_assert(requires { auto(auto(0)); });
+static_assert(requires { auto(auto(auto(0))); });
+
+static_assert(requires { auto{0}; });
+static_assert(requires { auto{auto{0}}; });
+static_assert(requires { auto{auto{auto{0}}}; });