]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
c++: refine CWG 2369 satisfaction vs non-dep convs [PR99599]
authorPatrick Palka <ppalka@redhat.com>
Fri, 8 Sep 2023 16:02:20 +0000 (12:02 -0400)
committerPatrick Palka <ppalka@redhat.com>
Fri, 8 Sep 2023 16:02:20 +0000 (12:02 -0400)
As described in detail in the PR, the CWG 2369 resolution has the
surprising consequence of introducing constraint recursion in seemingly
valid and innocent code.

This patch attempts to fix this surpising behavior for the majority of
problematic cases.  Rather than checking satisfaction before _all_
non-dependent conversions, as specified by the CWG resolution, this patch
makes us first check "safe" non-dependent conversions, then satisfaction,
then followed by other non-dependent conversions.  A conversion is
considered "safe" if computing it is guaranteed to not induce template
instantiation, and we conservatively determine this by checking for
user-declared constructors (resp. conversion functions) in the parm
(resp. arg) class type, roughly.

PR c++/99599

gcc/cp/ChangeLog:

* pt.cc (check_non_deducible_conversions): Add bool parameter
passed down to check_non_deducible_conversion.
(fn_type_unification): Call check_non_deducible_conversions
an extra time before satisfaction with noninst_only_p=true.
(conversion_may_instantiate_p): Define.
(check_non_deducible_conversion): Add bool parameter controlling
whether to compute only conversions that are guaranteed to
not induce template instantiation.

gcc/testsuite/ChangeLog:

* g++.dg/cpp2a/concepts-recursive-sat4.C: Make 'Int' non-aggregate
in order to preserve intent of the testcase.
* g++.dg/cpp2a/concepts-nondep4.C: New test.

gcc/cp/pt.cc
gcc/testsuite/g++.dg/cpp2a/concepts-nondep4.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/concepts-recursive-sat4.C

index 0790bbf948ff20ca3ded92f19cafcc6be935b111..838179d5fe3b683584d37ff287105c1d72ab0ad3 100644 (file)
@@ -151,7 +151,7 @@ static tree get_partial_spec_bindings (tree, tree, tree);
 static void tsubst_enum        (tree, tree, tree);
 static bool check_instantiated_args (tree, tree, tsubst_flags_t);
 static int check_non_deducible_conversion (tree, tree, unification_kind_t, int,
-                                          struct conversion **, bool);
+                                          struct conversion **, bool, bool);
 static int maybe_adjust_types_for_deduction (tree, unification_kind_t,
                                             tree*, tree*, tree);
 static int type_unification_real (tree, tree, tree, const tree *,
@@ -22321,7 +22321,8 @@ pack_deducible_p (tree parm, tree fn)
 static int
 check_non_deducible_conversions (tree parms, const tree *args, unsigned nargs,
                                 tree fn, unification_kind_t strict, int flags,
-                                struct conversion **convs, bool explain_p)
+                                struct conversion **convs, bool explain_p,
+                                bool noninst_only_p)
 {
   /* Non-constructor methods need to leave a conversion for 'this', which
      isn't included in nargs here.  */
@@ -22357,7 +22358,7 @@ check_non_deducible_conversions (tree parms, const tree *args, unsigned nargs,
          int lflags = conv_flags (ia, nargs, fn, arg, flags);
 
          if (check_non_deducible_conversion (parm, arg, strict, lflags,
-                                             conv_p, explain_p))
+                                             conv_p, explain_p, noninst_only_p))
            return 1;
        }
 
@@ -22657,6 +22658,16 @@ fn_type_unification (tree fn,
 
  deduced:
 
+  /* As a refinement of CWG2369, check first and foremost non-dependent
+     conversions that we know are not going to induce template instantiation
+     (PR99599).  */
+  if (strict == DEDUCE_CALL
+      && incomplete
+      && check_non_deducible_conversions (parms, args, nargs, fn, strict, flags,
+                                         convs, explain_p,
+                                         /*noninst_only_p=*/true))
+    goto fail;
+
   /* CWG2369: Check satisfaction before non-deducible conversions.  */
   if (!constraints_satisfied_p (fn, targs))
     {
@@ -22670,7 +22681,8 @@ fn_type_unification (tree fn,
      as the standard says that we substitute explicit args immediately.  */
   if (incomplete
       && check_non_deducible_conversions (parms, args, nargs, fn, strict, flags,
-                                         convs, explain_p))
+                                         convs, explain_p,
+                                         /*noninst_only_p=*/false))
     goto fail;
 
   /* All is well so far.  Now, check:
@@ -22897,6 +22909,59 @@ maybe_adjust_types_for_deduction (tree tparms,
   return result;
 }
 
+/* Return true if computing a conversion from FROM to TO might induce template
+   instantiation.  Conversely, if this predicate returns false then computing
+   the conversion definitely won't induce template instantiation.  */
+
+static bool
+conversion_may_instantiate_p (tree to, tree from)
+{
+  to = non_reference (to);
+  from = non_reference (from);
+
+  bool ptr_conv_p = false;
+  if (TYPE_PTR_P (to)
+      && TYPE_PTR_P (from))
+    {
+      to = TREE_TYPE (to);
+      from = TREE_TYPE (from);
+      ptr_conv_p = true;
+    }
+
+  /* If one of the types is a not-yet-instantiated class template
+     specialization, then computing the conversion might instantiate
+     it in order to inspect bases, conversion functions and/or
+     converting constructors.  */
+  if ((CLASS_TYPE_P (to)
+       && !COMPLETE_TYPE_P (to)
+       && CLASSTYPE_TEMPLATE_INSTANTIATION (to))
+      || (CLASS_TYPE_P (from)
+         && !COMPLETE_TYPE_P (from)
+         && CLASSTYPE_TEMPLATE_INSTANTIATION (from)))
+    return true;
+
+  /* Converting from one pointer type to another, or between
+     reference-related types, always yields a standard conversion.  */
+  if (ptr_conv_p || reference_related_p (to, from))
+    return false;
+
+  /* Converting to a non-aggregate class type will consider its
+     user-declared constructors, which might induce instantiation.  */
+  if (CLASS_TYPE_P (to)
+      && CLASSTYPE_NON_AGGREGATE (to))
+    return true;
+
+  /* Similarly, converting from a class type will consider its conversion
+     functions.  */
+  if (CLASS_TYPE_P (from)
+      && TYPE_HAS_CONVERSION (from))
+    return true;
+
+  /* Otherwise, computing this conversion definitely won't induce
+     template instantiation.  */
+  return false;
+}
+
 /* Subroutine of fn_type_unification.  PARM is a function parameter of a
    template which doesn't contain any deducible template parameters; check if
    ARG is a suitable match for it.  STRICT, FLAGS and EXPLAIN_P are as in
@@ -22905,7 +22970,7 @@ maybe_adjust_types_for_deduction (tree tparms,
 static int
 check_non_deducible_conversion (tree parm, tree arg, unification_kind_t strict,
                                int flags, struct conversion **conv_p,
-                               bool explain_p)
+                               bool explain_p, bool noninst_only_p)
 {
   tree type;
 
@@ -22925,6 +22990,18 @@ check_non_deducible_conversion (tree parm, tree arg, unification_kind_t strict,
     }
   else if (strict == DEDUCE_CALL)
     {
+      if (conv_p && *conv_p)
+       {
+         /* This conversion was already computed earlier (when
+            computing only non-instantiating conversions).  */
+         gcc_checking_assert (!noninst_only_p);
+         return unify_success (explain_p);
+       }
+
+      if (noninst_only_p
+         && conversion_may_instantiate_p (parm, type))
+       return unify_success (explain_p);
+
       bool ok = false;
       tree conv_arg = TYPE_P (arg) ? NULL_TREE : arg;
       if (conv_p)
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-nondep4.C b/gcc/testsuite/g++.dg/cpp2a/concepts-nondep4.C
new file mode 100644 (file)
index 0000000..9a8db55
--- /dev/null
@@ -0,0 +1,16 @@
+// PR c++/99599
+// { dg-do compile { target c++20 } }
+
+struct foo_tag { };
+struct bar_tag { };
+
+template<class T>
+concept fooable = requires(T it) { invoke_tag(foo_tag{}, it); };
+
+template<class T> void invoke_tag(foo_tag, T);
+template<class T> void invoke_tag(bar_tag, T) requires fooable<T>;
+
+int main() {
+  invoke_tag(foo_tag{}, 0);
+  invoke_tag(bar_tag{}, 0);
+}
index 18d126e05ea4cee9cfa74088265c526621bf13fe..08f206d3634104303dbbbf23fd764c70ab0017ae 100644 (file)
@@ -1,3 +1,4 @@
+// Verify we diagnose constraint recursion.
 // PR c++/96840
 // { dg-do compile { target c++20 } }
 
@@ -6,6 +7,7 @@ template <class T, class U> concept C = requires(T t, U u) { t * u; };
 // { dg-error "depends on itself" "" { target *-*-* } .-2 }
 
 template <class Rep> struct Int {
+  Int(); // make the class non-aggregate in light of PR99599 fix
   template <class T> requires C<T, Rep> friend void operator*(T, Int) { }
   template <class T> requires C<T, Rep> friend void operator*(Int, T) { }
 };