[pushed] c++: alias template argument conversion [PR112632]

Message ID 20240119183336.3505523-1-jason@redhat.com
State Accepted
Headers
Series [pushed] c++: alias template argument conversion [PR112632] |

Checks

Context Check Description
snail/gcc-patch-check success Github commit url

Commit Message

Jason Merrill Jan. 19, 2024, 6:33 p.m. UTC
  Tested x86_64-pc-linux-gnu, applying to trunk.

-- 8< --

We've had a problem with lost conversions to template parameter types for a
while now; looking at this PR, it occurred to me that the problem is really
with alias (and concept) templates, since we do substitution of dependent
arguments into them in a way that we don't for other templates.  And fixing
that specific problem is a lot simpler than adding IMPLICIT_CONV_EXPR around
all dependent template arguments the way I gave up on for 111357.

The other part of the fix was changing tsubst_expr to actually call
convert_nontype_argument instead of assuming it will eventually happen.

I waffled about stripping the forced conversion when !force_conv
vs. skipping them in iterative_hash_template_arg and
template_args_equal (like we already do for some other conversions) and
decided to go with the former, but that isn't a strong preference if it
turns out to be somehow problematic.

	PR c++/112632
	PR c++/112594
	PR c++/111357
	PR c++/104594
	PR c++/67898

gcc/cp/ChangeLog:

	* cp-tree.h (IMPLICIT_CONV_EXPR_FORCED): New.
	* pt.cc (expand_integer_pack): Remove 111357 workaround.
	(maybe_convert_nontype_argument): Add force parm.
	(convert_template_argument): Handle alias template args
	specially.
	(tsubst_expr): Don't ignore IMPLICIT_CONV_EXPR_NONTYPE_ARG.
	* error.cc (dump_expr) [CASE_CONVERT]: Handle null optype.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp0x/alias-decl-nontype1.C: New test.
	* g++.dg/cpp2a/concepts-narrowing1.C: New test.
	* g++.dg/cpp2a/nontype-class63.C: New test.
	* g++.dg/cpp2a/nontype-class63a.C: New test.
---
 gcc/cp/cp-tree.h                              |  5 ++
 gcc/cp/error.cc                               |  4 +-
 gcc/cp/pt.cc                                  | 48 +++++++++++++------
 .../g++.dg/cpp0x/alias-decl-nontype1.C        |  9 ++++
 .../g++.dg/cpp2a/concepts-narrowing1.C        | 16 +++++++
 gcc/testsuite/g++.dg/cpp2a/nontype-class63.C  | 24 ++++++++++
 gcc/testsuite/g++.dg/cpp2a/nontype-class63a.C | 24 ++++++++++
 7 files changed, 114 insertions(+), 16 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp0x/alias-decl-nontype1.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-narrowing1.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/nontype-class63.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/nontype-class63a.C


base-commit: e04376b336502016456eaf4e90c3ea792c77c8df
  

Patch

diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index d9b14d7c4f5..60e6dafc549 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -4717,6 +4717,11 @@  get_vec_init_expr (tree t)
 #define IMPLICIT_CONV_EXPR_BRACED_INIT(NODE) \
   (TREE_LANG_FLAG_2 (IMPLICIT_CONV_EXPR_CHECK (NODE)))
 
+/* True if NODE represents a conversion forced to be represented in
+   maybe_convert_nontype_argument, i.e. for an alias template.  */
+#define IMPLICIT_CONV_EXPR_FORCED(NODE) \
+  (TREE_LANG_FLAG_3 (IMPLICIT_CONV_EXPR_CHECK (NODE)))
+
 /* Nonzero means that an object of this type cannot be initialized using
    an initializer list.  */
 #define CLASSTYPE_NON_AGGREGATE(NODE) \
diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
index 52e24fb086c..d3fcac70ea1 100644
--- a/gcc/cp/error.cc
+++ b/gcc/cp/error.cc
@@ -2673,6 +2673,8 @@  dump_expr (cxx_pretty_printer *pp, tree t, int flags)
 
 	tree ttype = TREE_TYPE (t);
 	tree optype = TREE_TYPE (op);
+	if (!optype)
+	  optype = unknown_type_node;
 
 	if (TREE_CODE (ttype) != TREE_CODE (optype)
 	    && INDIRECT_TYPE_P (ttype)
@@ -2691,7 +2693,7 @@  dump_expr (cxx_pretty_printer *pp, tree t, int flags)
 	    else
 	      dump_unary_op (pp, "&", t, flags);
 	  }
-	else if (!same_type_p (TREE_TYPE (op), TREE_TYPE (t)))
+	else if (!same_type_p (optype, ttype))
 	  {
 	    /* It is a cast, but we cannot tell whether it is a
 	       reinterpret or static cast. Use the C style notation.  */
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index f82d018c981..fbbca469219 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -3760,13 +3760,6 @@  expand_integer_pack (tree call, tree args, tsubst_flags_t complain,
     {
       if (hi != ohi)
 	{
-	  /* Work around maybe_convert_nontype_argument not doing this for
-	     dependent arguments.  Don't use IMPLICIT_CONV_EXPR_NONTYPE_ARG
-	     because that will make tsubst_expr ignore it.  */
-	  tree type = tsubst (TREE_TYPE (ohi), args, complain, in_decl);
-	  if (!TREE_TYPE (hi) || !same_type_p (type, TREE_TYPE (hi)))
-	    hi = build1 (IMPLICIT_CONV_EXPR, type, hi);
-
 	  call = copy_node (call);
 	  CALL_EXPR_ARG (call, 0) = hi;
 	}
@@ -8457,23 +8450,30 @@  convert_wildcard_argument (tree parm, tree arg)
    conversion for the benefit of cp_tree_equal.  */
 
 static tree
-maybe_convert_nontype_argument (tree type, tree arg)
+maybe_convert_nontype_argument (tree type, tree arg, bool force)
 {
   /* Auto parms get no conversion.  */
   if (type_uses_auto (type))
     return arg;
+  /* ??? Do we need to push the IMPLICIT_CONV_EXPR into the pack expansion?
+     That would complicate other things, and it doesn't seem necessary.  */
+  if (TREE_CODE (arg) == EXPR_PACK_EXPANSION)
+    return arg;
   /* We don't need or want to add this conversion now if we're going to use the
      argument for deduction.  */
-  if (value_dependent_expression_p (arg))
+  if (!value_dependent_expression_p (arg))
+    force = false;
+  else if (!force)
     return arg;
 
   type = cv_unqualified (type);
   tree argtype = TREE_TYPE (arg);
-  if (same_type_p (type, argtype))
+  if (argtype && same_type_p (type, argtype))
     return arg;
 
   arg = build1 (IMPLICIT_CONV_EXPR, type, arg);
   IMPLICIT_CONV_EXPR_NONTYPE_ARG (arg) = true;
+  IMPLICIT_CONV_EXPR_FORCED (arg) = force;
   return arg;
 }
 
@@ -8741,6 +8741,22 @@  convert_template_argument (tree parm,
       if (t != TREE_TYPE (parm))
 	t = canonicalize_type_argument (t, complain);
 
+      /* We need to handle arguments for alias or concept templates
+	 differently: we need to force building an IMPLICIT_CONV_EXPR, because
+	 these arguments are going to be substituted directly into the
+	 dependent type; they might not get another chance at
+	 convert_nontype_argument.  But if the argument ends up here again for
+	 a template that isn't one of those, remove the conversion for
+	 consistency between naming the same dependent type directly or through
+	 an alias.  */
+      bool force_conv = in_decl && (DECL_ALIAS_TEMPLATE_P (in_decl)
+				    || concept_definition_p (in_decl));
+      if (!force_conv
+	  && TREE_CODE (orig_arg) == IMPLICIT_CONV_EXPR
+	  && IMPLICIT_CONV_EXPR_FORCED (orig_arg)
+	  && same_type_p (TREE_TYPE (orig_arg), t))
+	orig_arg = TREE_OPERAND (orig_arg, 0);
+
       if (!type_dependent_expression_p (orig_arg)
 	  && !uses_template_parms (t))
 	/* We used to call digest_init here.  However, digest_init
@@ -8757,10 +8773,9 @@  convert_template_argument (tree parm,
       else
 	{
 	  val = canonicalize_expr_argument (orig_arg, complain);
-	  val = maybe_convert_nontype_argument (t, val);
+	  val = maybe_convert_nontype_argument (t, val, force_conv);
 	}
 
-
       if (val == NULL_TREE)
 	val = error_mark_node;
       else if (val == error_mark_node && (complain & tf_error))
@@ -20056,9 +20071,12 @@  tsubst_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl)
 	    RETURN (retval);
 	  }
 	if (IMPLICIT_CONV_EXPR_NONTYPE_ARG (t))
-	  /* We'll pass this to convert_nontype_argument again, we don't need
-	     to actually perform any conversion here.  */
-	  RETURN (expr);
+	  {
+	    tree r = convert_nontype_argument (type, expr, complain);
+	    if (r == NULL_TREE)
+	      r = error_mark_node;
+	    RETURN (r);
+	  }
 	int flags = LOOKUP_IMPLICIT;
 	if (IMPLICIT_CONV_EXPR_DIRECT_INIT (t))
 	  flags = LOOKUP_NORMAL;
diff --git a/gcc/testsuite/g++.dg/cpp0x/alias-decl-nontype1.C b/gcc/testsuite/g++.dg/cpp0x/alias-decl-nontype1.C
new file mode 100644
index 00000000000..3a9f0de834e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp0x/alias-decl-nontype1.C
@@ -0,0 +1,9 @@ 
+// PR c++/67898
+// { dg-do compile { target c++11 } }
+
+template<class T, T V, class = decltype(V)> struct A;
+template<class U> using B = A<U, 0>;
+
+using type = A<bool, 0>;
+using type = B<bool>;    // incorrectly resolves to A<bool, 0, int>
+                         // instead of A<bool, 0, bool>
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-narrowing1.C b/gcc/testsuite/g++.dg/cpp2a/concepts-narrowing1.C
new file mode 100644
index 00000000000..dcfe2729f70
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-narrowing1.C
@@ -0,0 +1,16 @@ 
+// PR c++/104594
+// { dg-do compile { target c++20 } }
+
+template <unsigned char DIM_FROM>
+concept Geometry = (DIM_FROM == -1);
+
+template <class INIT>
+requires Geometry<INIT::n>
+auto GaussNewton(const INIT& init) -> void {}
+
+template<int N>
+struct X {
+  static constexpr int n = N;
+};
+
+int main() { GaussNewton(X<-1>{}); } // { dg-error "no match|narrowing" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-class63.C b/gcc/testsuite/g++.dg/cpp2a/nontype-class63.C
new file mode 100644
index 00000000000..2e617396912
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/nontype-class63.C
@@ -0,0 +1,24 @@ 
+// PR c++/112632
+// { dg-do compile { target c++20 } }
+
+template<typename T>
+inline constexpr bool C = true;
+
+struct n {
+  constexpr n(int a) : i(a) {}
+  int i;
+};
+
+template<n N>
+using get_n_i_type = decltype(N.i);
+
+template<int X>
+int f() {
+  using iii = get_n_i_type<X>;
+#if 1  // Change to 0 and this compiles
+  static_assert(C<iii>);
+#endif
+  return iii{};
+}
+
+template int f<3>();
diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-class63a.C b/gcc/testsuite/g++.dg/cpp2a/nontype-class63a.C
new file mode 100644
index 00000000000..489a90d9fdb
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/nontype-class63a.C
@@ -0,0 +1,24 @@ 
+// PR c++/112594
+// { dg-do compile { target c++20 } }
+
+template<typename T>
+concept C = true;
+
+struct n {
+  constexpr n(int) {}
+  static int i;
+};
+
+template<n N>
+using get_n_i_type = decltype(N.i);
+
+template<int X>
+int f() {
+  using iii = get_n_i_type<X>;
+#if 1  // Change to 0 and this compiles
+  static_assert(C<iii>);
+#endif
+  return iii{};
+}
+
+template int f<3>();