On Wed, Aug 27, 2025 at 04:43:57PM +0200, Jason Merrill wrote:
> On 8/27/25 8:29 AM, Nathaniel Shead wrote:
> > Bootstrapped and regtested on x86_64-pc-linux-gnu, OK for trunk?
> 
> OK.
> 

While playing around again quickly before submitting I noticed that this
missed some cases with handling things like using-decls and templates;
here's a more complete patch that should handle this more robustly and
is less likely to give unhelpful messages.

Bootstrapped and regtested on x86_64-pc-linux-gnu, OK for trunk?

-- >8 --

The confusion in the PR arose because the definition of 'User' in a
separate named module did not provide an implementation for the
forward-declaration in the global module.  This seems likely to be a
common mistake while people are transitioning to modules, so this patch
adds an explanatory note.

While I was looking at this I also noticed that the existing handling of
partial specialisations for this note was wrong (we pointed at the
primary template declaration rather than the relevant partial spec), so
this patch fixes that up, and also gives a more precise error message
for using a template other than by self-reference while it's being
defined.

        PR c++/119844

gcc/cp/ChangeLog:

        * typeck2.cc (cxx_incomplete_type_inform): Add explanation when
        a similar type is complete but attached to a different module.
        Also fix handling of partial specs and templates.

gcc/testsuite/ChangeLog:

        * g++.dg/modules/pr119844_a.C: New test.
        * g++.dg/modules/pr119844_b.C: New test.

Signed-off-by: Nathaniel Shead <nathanielosh...@gmail.com>
---
 gcc/cp/typeck2.cc                            | 89 +++++++++++++++++++-
 gcc/testsuite/g++.dg/modules/pr119844_a.C    | 27 ++++++
 gcc/testsuite/g++.dg/modules/pr119844_b.C    | 57 +++++++++++++
 gcc/testsuite/g++.dg/template/incomplete13.C | 17 ++++
 4 files changed, 186 insertions(+), 4 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/modules/pr119844_a.C
 create mode 100644 gcc/testsuite/g++.dg/modules/pr119844_b.C
 create mode 100644 gcc/testsuite/g++.dg/template/incomplete13.C

diff --git a/gcc/cp/typeck2.cc b/gcc/cp/typeck2.cc
index faaf1df6158..d77de9212ed 100644
--- a/gcc/cp/typeck2.cc
+++ b/gcc/cp/typeck2.cc
@@ -281,15 +281,96 @@ cxx_incomplete_type_inform (const_tree type)
   location_t loc = DECL_SOURCE_LOCATION (TYPE_MAIN_DECL (type));
   tree ptype = strip_top_quals (CONST_CAST_TREE (type));
 
+  /* When defining a template, current_class_type will be the pattern on
+     the template definition, while non-self-reference usages of this
+     template will be an instantiation; we should pull out the pattern to
+     compare against.  And for partial specs we should use the loc of the
+     partial spec rather than the primary template.  */
+  tree ttype = NULL_TREE;
+  tree tinfo = TYPE_TEMPLATE_INFO (ptype);
+  if (tinfo)
+    {
+      tree tmpl = TI_TEMPLATE (tinfo);
+      if (PRIMARY_TEMPLATE_P (tmpl) && TI_PARTIAL_INFO (tinfo))
+       {
+         tree partial = TI_TEMPLATE (TI_PARTIAL_INFO (tinfo));
+         loc = DECL_SOURCE_LOCATION (partial);
+         ttype = TREE_TYPE (partial);
+       }
+      else
+       ttype = TREE_TYPE (tmpl);
+    }
+
   if (current_class_type
       && TYPE_BEING_DEFINED (current_class_type)
-      && same_type_p (ptype, current_class_type))
+      && (same_type_p (ptype, current_class_type)
+         || (ttype && same_type_p (ttype, current_class_type))))
     inform (loc, "definition of %q#T is not complete until "
            "the closing brace", ptype);
-  else if (!TYPE_TEMPLATE_INFO (ptype))
-    inform (loc, "forward declaration of %q#T", ptype);
   else
-    inform (loc, "declaration of %q#T", ptype);
+    {
+      if (!tinfo)
+       inform (loc, "forward declaration of %q#T", ptype);
+      else
+       inform (loc, "declaration of %q#T", ptype);
+
+      /* If there's a similar-looking complete type attached
+        to a different module, point at that as a suggestion.  */
+      if (modules_p () && TYPE_NAMESPACE_SCOPE_P (ptype))
+       {
+         tree result = lookup_qualified_name (CP_TYPE_CONTEXT (ptype),
+                                              TYPE_IDENTIFIER (ptype),
+                                              LOOK_want::TYPE);
+         if (TREE_CODE (result) == TREE_LIST)
+           for (; result; result = TREE_CHAIN (result))
+             {
+               tree cand = TREE_VALUE (result);
+
+               /* Typedefs are not likely intended to correspond.  */
+               if (is_typedef_decl (STRIP_TEMPLATE (cand))
+                   || DECL_ALIAS_TEMPLATE_P (cand))
+                 continue;
+
+               /* Only look at templates if type was a template.  */
+               if ((tinfo != nullptr) != (TREE_CODE (cand) == TEMPLATE_DECL))
+                 continue;
+
+               /* If we're looking for a template specialisation,
+                  only consider matching specialisations.  */
+               if (tinfo)
+                 {
+                   tree t = lookup_template_class (cand, TI_ARGS (tinfo),
+                                                   NULL_TREE, NULL_TREE,
+                                                   tf_none);
+                   if (t == error_mark_node
+                       || !CLASS_TYPE_P (t)
+                       || TYPE_BEING_DEFINED (t))
+                     continue;
+
+                   if (CLASSTYPE_TEMPLATE_INSTANTIATION (t))
+                     {
+                       /* An uninstantiated template: check if there is a
+                          pattern that could be used.  We don't want to
+                          call instantiate_class_template as that could
+                          cause further errors; this is just a hint.  */
+                       tree part = most_specialized_partial_spec (t, tf_none);
+                       cand = (part ? TI_TEMPLATE (part)
+                               : CLASSTYPE_TI_TEMPLATE (t));
+                     }
+                   else
+                     cand = TYPE_NAME (t);
+                 }
+               
+               if (!COMPLETE_TYPE_P (TREE_TYPE (cand)))
+                 continue;
+
+               inform (DECL_SOURCE_LOCATION (cand),
+                       "%q#T has a definition but does not correspond with "
+                       "%q#T because it is attached to a different module",
+                       TREE_TYPE (cand), ptype);
+             }
+       }
+    }
 }
 
 /* Print an error message for invalid use of an incomplete type.
diff --git a/gcc/testsuite/g++.dg/modules/pr119844_a.C 
b/gcc/testsuite/g++.dg/modules/pr119844_a.C
new file mode 100644
index 00000000000..24504e00bf7
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/pr119844_a.C
@@ -0,0 +1,27 @@
+// PR c++/119844
+// { dg-additional-options "-fmodules" }
+// { dg-module-cmi M }
+
+export module M;
+
+struct S { int value; };
+
+export struct A { int value; };
+export using B = S;  // typedef, shouldn't correspond
+export template <typename T> struct C { int value; };  // template vs. 
non-template
+
+// we use static_assert(false) to ensure we don't try to complete the body
+// and get unrelated errors while reporting
+export template <typename T> struct D { static_assert(false); };
+export template <typename T> using E = S;  // typedef, shouldn't correspond
+
+export template <typename T> struct F;
+template <> struct F<int> { int value; };
+
+export template <typename T> struct G { static_assert(false); };
+
+export template <typename T> struct H;
+template <typename T> struct H<const T> { static_assert(false); };
+#if __cpp_concepts >= 201907L
+template <typename T> requires true struct H<const T> { static_assert(false); 
};
+#endif
diff --git a/gcc/testsuite/g++.dg/modules/pr119844_b.C 
b/gcc/testsuite/g++.dg/modules/pr119844_b.C
new file mode 100644
index 00000000000..ad945afa491
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/pr119844_b.C
@@ -0,0 +1,57 @@
+// PR c++/119844
+// { dg-additional-options "-fmodules" }
+
+struct A; // { dg-message "declaration" }
+struct B; // { dg-message "declaration" }
+struct C; // { dg-message "declaration" }
+
+template <typename T> struct D; // { dg-message "declaration" }
+template <typename T> struct E; // { dg-message "declaration" }
+
+template <typename T> struct F; // { dg-message "declaration" }
+
+template <typename T> struct G { int value; };  // { dg-bogus "module" }
+template <> struct G<int>; // { dg-message "declaration" }
+// { dg-bogus "module" "" { target *-*-* } .-1 }
+
+template <typename T> struct H { int value; };  // { dg-bogus "module" }
+template <typename T> struct H<const T>;  // { dg-message "declaration" }
+// { dg-bogus "module" "" { target *-*-* } .-1 }
+
+struct MainWindow {
+  A* a;
+  B* b;
+  C* c;
+
+  D<int>* d;
+  E<int>* e;
+  F<int>* f;
+
+  G<int>* g;
+  H<const int>* h;
+};
+
+import M;
+
+int foo(MainWindow m) {
+  int result = 0;
+  result += m.a->value; // { dg-error "incomplete" }
+  result += m.b->value; // { dg-error "incomplete" }
+  result += m.c->value; // { dg-error "incomplete" }
+  result += m.d->value; // { dg-error "incomplete" }
+  result += m.e->value; // { dg-error "incomplete" }
+  result += m.f->value; // { dg-error "incomplete" }
+  result += m.g->value; // { dg-error "incomplete" }
+  result += m.h->value; // { dg-error "incomplete" }
+  return result;
+}
+
+// { dg-message "A@M" "" { target *-*-* } 0 }
+// { dg-bogus   "B@M" "" { target *-*-* } 0 }
+// { dg-bogus   "C@M" "" { target *-*-* } 0 }
+// { dg-message "D@M" "" { target *-*-* } 0 }
+// { dg-bogus   "E@M" "" { target *-*-* } 0 }
+// { dg-message "F@M" "" { target *-*-* } 0 }
+// { dg-message "G@M" "" { target *-*-* } 0 }
+// { dg-message "H@M" "" { target *-*-* } 0 }
+
diff --git a/gcc/testsuite/g++.dg/template/incomplete13.C 
b/gcc/testsuite/g++.dg/template/incomplete13.C
new file mode 100644
index 00000000000..1e7eecd39f7
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/incomplete13.C
@@ -0,0 +1,17 @@
+// { dg-do compile }
+
+template <typename T> struct A {};  // { dg-bogus "declaration" }
+template <typename T> struct A<T*> {  // { dg-message "closing brace" }
+  A<int*> a;  // { dg-error "incomplete" }
+};
+
+template <typename T> struct B;
+template <typename T> struct B {  // { dg-message "closing brace" }
+  B<int*> b;  // { dg-error "incomplete" }
+};
+
+template <typename T> struct C { int value; };  // { dg-bogus "declaration" }
+template <typename T> struct C<T*>;  // { dg-message "declaration" }
+int test(C<int*>& b) {
+  return b.value;  // { dg-error "incomplete" }
+}
-- 
2.47.0

Reply via email to