]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
c++/modules: Add explanatory note for incomplete types with definition in different...
authorNathaniel Shead <nathanieloshead@gmail.com>
Wed, 27 Aug 2025 12:24:43 +0000 (22:24 +1000)
committerNathaniel Shead <nathanieloshead@gmail.com>
Thu, 28 Aug 2025 12:33:25 +0000 (22:33 +1000)
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 <nathanieloshead@gmail.com>
gcc/cp/typeck2.cc
gcc/testsuite/g++.dg/modules/pr119844_a.C [new file with mode: 0644]
gcc/testsuite/g++.dg/modules/pr119844_b.C [new file with mode: 0644]
gcc/testsuite/g++.dg/template/incomplete13.C [new file with mode: 0644]

index faaf1df6158345d3b2eba4751e47bc5ba1498120..d77de9212ed3e1c022b2df6527d36dbf1c85d9c6 100644 (file)
@@ -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 (file)
index 0000000..24504e0
--- /dev/null
@@ -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 (file)
index 0000000..ad945af
--- /dev/null
@@ -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 (file)
index 0000000..1e7eecd
--- /dev/null
@@ -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" }
+}