]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
c++: canonicity of fn types w/ complex eh specs [PR115159]
authorPatrick Palka <ppalka@redhat.com>
Wed, 22 May 2024 21:45:04 +0000 (17:45 -0400)
committerPatrick Palka <ppalka@redhat.com>
Wed, 22 May 2024 21:45:04 +0000 (17:45 -0400)
Here the member functions QList::g and QList::h are given the same
function type by build_cp_fntype_variant since their noexcept-specs are
equivalent according to cp_tree_equal.  In doing so however this means
that the function type of QList::h refers to a function parameter from
QList::g, which ends up confusing modules streaming.

I'm not sure if modules can be fixed to handle this situation, but
regardless it seems weird in principle that a function parameter can
escape in such a way.  The analogous situation with a trailing return
type and decltype

  auto g(QList &other) -> decltype(f(other));
  auto h(QList &other) -> decltype(f(other));

behaves better because we don't canonicalize decltype, and so the
function types of g and h are non-canonical and therefore not shared.

In light of this, it seems natural to treat function types with complex
noexcept-specs as non-canonical as well so that each such function
declaration is given a unique function type node.  (The main benefit of
type canonicalization is to speed up repeated type comparisons, but it
should be rare to repeatedly compare two otherwise compatible function
types with complex noexcept-specs.)

To that end, this patch strengthens the ce_exact case of comp_except_specs
to require identity instead of equivalence of the noexcept-spec so that
build_cp_fntype_variant doesn't reuse a variant when it shouldn't.  In
turn we need to use structural equality for types with a complex eh spec.
This lets us get rid of the tricky handling of canonical types when updating
unparsed noexcept-spec variants.

PR c++/115159

gcc/cp/ChangeLog:

* tree.cc (build_cp_fntype_variant): Always use structural
equality for types with a complex exception specification.
(fixup_deferred_exception_variants): Use structural equality
for adjusted variants.
* typeck.cc (comp_except_specs): Require == instead of
cp_tree_equal for ce_exact noexcept-spec comparison.

gcc/testsuite/ChangeLog:

* g++.dg/modules/noexcept-2_a.H: New test.
* g++.dg/modules/noexcept-2_b.C: New test.

Reviewed-by: Jason Merrill <jason@redhat.com>
gcc/cp/tree.cc
gcc/cp/typeck.cc
gcc/testsuite/g++.dg/modules/noexcept-2_a.H [new file with mode: 0644]
gcc/testsuite/g++.dg/modules/noexcept-2_b.C [new file with mode: 0644]

index 9d37d255d8d5cb77185fe6a5fe1990c66d4c81f3..4d87661b4add533ccc395975aae2a5986c78a24c 100644 (file)
@@ -2793,9 +2793,13 @@ build_cp_fntype_variant (tree type, cp_ref_qualifier rqual,
 
   /* Canonicalize the exception specification.  */
   tree cr = flag_noexcept_type ? canonical_eh_spec (raises) : NULL_TREE;
+  bool complex_eh_spec_p = (cr && cr != noexcept_true_spec
+                           && !UNPARSED_NOEXCEPT_SPEC_P (cr));
 
-  if (TYPE_STRUCTURAL_EQUALITY_P (type))
-    /* Propagate structural equality. */
+  if (TYPE_STRUCTURAL_EQUALITY_P (type) || complex_eh_spec_p)
+    /* Propagate structural equality.  And always use structural equality
+       for function types with a complex noexcept-spec since their identity
+       may depend on e.g. whether comparing_specializations is set.  */
     SET_TYPE_STRUCTURAL_EQUALITY (v);
   else if (TYPE_CANONICAL (type) != type || cr != raises || late)
     /* Build the underlying canonical type, since it is different
@@ -2812,55 +2816,23 @@ build_cp_fntype_variant (tree type, cp_ref_qualifier rqual,
 /* TYPE is a function or method type with a deferred exception
    specification that has been parsed to RAISES.  Fixup all the type
    variants that are affected in place.  Via decltype &| noexcept
-   tricks, the unparsed spec could have escaped into the type system.
-   The general case is hard to fixup canonical types for.  */
+   tricks, the unparsed spec could have escaped into the type system.  */
 
 void
 fixup_deferred_exception_variants (tree type, tree raises)
 {
   tree original = TYPE_RAISES_EXCEPTIONS (type);
-  tree cr = flag_noexcept_type ? canonical_eh_spec (raises) : NULL_TREE;
 
   gcc_checking_assert (UNPARSED_NOEXCEPT_SPEC_P (original));
 
-  /* Though sucky, this walk will process the canonical variants
-     first.  */
-  tree prev = NULL_TREE;
   for (tree variant = TYPE_MAIN_VARIANT (type);
-       variant; prev = variant, variant = TYPE_NEXT_VARIANT (variant))
+       variant; variant = TYPE_NEXT_VARIANT (variant))
     if (TYPE_RAISES_EXCEPTIONS (variant) == original)
       {
        gcc_checking_assert (variant != TYPE_MAIN_VARIANT (type));
 
-       if (!TYPE_STRUCTURAL_EQUALITY_P (variant))
-         {
-           cp_cv_quals var_quals = TYPE_QUALS (variant);
-           cp_ref_qualifier rqual = type_memfn_rqual (variant);
-
-           /* If VARIANT would become a dup (cp_check_qualified_type-wise)
-              of an existing variant in the variant list of TYPE after its
-              exception specification has been parsed, elide it.  Otherwise,
-              build_cp_fntype_variant could use it, leading to "canonical
-              types differ for identical types."  */
-           tree v = TYPE_MAIN_VARIANT (type);
-           for (; v; v = TYPE_NEXT_VARIANT (v))
-             if (cp_check_qualified_type (v, variant, var_quals,
-                                          rqual, cr, false))
-               {
-                 /* The main variant will not match V, so PREV will never
-                    be null.  */
-                 TYPE_NEXT_VARIANT (prev) = TYPE_NEXT_VARIANT (variant);
-                 break;
-               }
-           TYPE_RAISES_EXCEPTIONS (variant) = raises;
-
-           if (!v)
-             v = build_cp_fntype_variant (TYPE_CANONICAL (variant),
-                                          rqual, cr, false);
-           TYPE_CANONICAL (variant) = TYPE_CANONICAL (v);
-         }
-       else
-         TYPE_RAISES_EXCEPTIONS (variant) = raises;
+       SET_TYPE_STRUCTURAL_EQUALITY (variant);
+       TYPE_RAISES_EXCEPTIONS (variant) = raises;
 
        if (!TYPE_DEPENDENT_P (variant))
          /* We no longer know that it's not type-dependent.  */
index 5f16994300fd4d0b64b21ecf6352c3860e2209f9..d7fa6e0dd963ce39d4832db878a2c531d6c9a07d 100644 (file)
@@ -1227,7 +1227,9 @@ comp_except_specs (const_tree t1, const_tree t2, int exact)
   if ((t1 && TREE_PURPOSE (t1))
       || (t2 && TREE_PURPOSE (t2)))
     return (t1 && t2
-           && cp_tree_equal (TREE_PURPOSE (t1), TREE_PURPOSE (t2)));
+           && (exact == ce_exact
+               ? TREE_PURPOSE (t1) == TREE_PURPOSE (t2)
+               : cp_tree_equal (TREE_PURPOSE (t1), TREE_PURPOSE (t2))));
 
   if (t1 == NULL_TREE)                    /* T1 is ...  */
     return t2 == NULL_TREE || exact == ce_derived;
diff --git a/gcc/testsuite/g++.dg/modules/noexcept-2_a.H b/gcc/testsuite/g++.dg/modules/noexcept-2_a.H
new file mode 100644 (file)
index 0000000..b7144f4
--- /dev/null
@@ -0,0 +1,24 @@
+// PR c++/115159
+// { dg-additional-options -fmodule-header }
+// { dg-module-cmi {} }
+
+struct QDebug;
+
+template<class T> void f(T);
+
+template<class T> struct QList {
+  QDebug g(QList &other) noexcept(noexcept(f(other)));
+  QDebug h(QList &other) noexcept(noexcept(f(other)));
+};
+
+struct QObjectData {
+  QList<int> children;
+};
+
+struct QIODevice {
+  QObjectData d_ptr;
+};
+
+struct QDebug {
+  QDebug(QIODevice);
+};
diff --git a/gcc/testsuite/g++.dg/modules/noexcept-2_b.C b/gcc/testsuite/g++.dg/modules/noexcept-2_b.C
new file mode 100644 (file)
index 0000000..d34c63a
--- /dev/null
@@ -0,0 +1,4 @@
+// PR c++/115159
+// { dg-additional-options "-fmodules-ts -fno-module-lazy" }
+
+import "noexcept-2_a.H";