https://gcc.gnu.org/g:10418a6cbd34e0a4081f6dcdf8c36a9592aaf318
commit r16-3433-g10418a6cbd34e0a4081f6dcdf8c36a9592aaf318 Author: Nathaniel Shead <nathanielosh...@gmail.com> Date: Wed Aug 27 22:24:43 2025 +1000 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 <nathanielosh...@gmail.com> Diff: --- 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(-) diff --git a/gcc/cp/typeck2.cc b/gcc/cp/typeck2.cc index faaf1df61583..d77de9212ed3 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 000000000000..24504e00bf76 --- /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 000000000000..ad945afa491e --- /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 000000000000..1e7eecd39f7d --- /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" } +}