From: Nathaniel Shead Date: Wed, 27 Aug 2025 12:24:43 +0000 (+1000) Subject: c++/modules: Add explanatory note for incomplete types with definition in different... X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=10418a6cbd34e0a4081f6dcdf8c36a9592aaf318;p=thirdparty%2Fgcc.git c++/modules: Add explanatory note for incomplete types with definition in different module [PR119844] 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 --- 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 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 struct D { static_assert(false); }; +export template using E = S; // typedef, shouldn't correspond + +export template struct F; +template <> struct F { int value; }; + +export template struct G { static_assert(false); }; + +export template struct H; +template struct H { static_assert(false); }; +#if __cpp_concepts >= 201907L +template requires true struct H { 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 struct D; // { dg-message "declaration" } +template struct E; // { dg-message "declaration" } + +template struct F; // { dg-message "declaration" } + +template struct G { int value; }; // { dg-bogus "module" } +template <> struct G; // { dg-message "declaration" } +// { dg-bogus "module" "" { target *-*-* } .-1 } + +template struct H { int value; }; // { dg-bogus "module" } +template struct H; // { dg-message "declaration" } +// { dg-bogus "module" "" { target *-*-* } .-1 } + +struct MainWindow { + A* a; + B* b; + C* c; + + D* d; + E* e; + F* f; + + G* g; + H* 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 struct A {}; // { dg-bogus "declaration" } +template struct A { // { dg-message "closing brace" } + A a; // { dg-error "incomplete" } +}; + +template struct B; +template struct B { // { dg-message "closing brace" } + B b; // { dg-error "incomplete" } +}; + +template struct C { int value; }; // { dg-bogus "declaration" } +template struct C; // { dg-message "declaration" } +int test(C& b) { + return b.value; // { dg-error "incomplete" } +}