diff mbox series

c++/modules: Handle forward-declared class types

Message ID 6716451e.170a0220.359288.5a3c@mx.google.com
State New
Headers show
Series c++/modules: Handle forward-declared class types | expand

Commit Message

Nathaniel Shead Oct. 21, 2024, 12:12 p.m. UTC
Bootstrapped and regtested on x86_64-pc-linux-gnu, OK for trunk?

-- >8 --

In some cases we can access members of a namespace-scope class without
ever having performed name-lookup on it; this can occur when a
forward-declaration of the class is used as a return type, for
instance, or with PIMPL.

One possible approach would be to do name lookup in complete_type to
force lazy loading to occur, but this seems overly expensive for a
relatively rare case.  Instead, this patch generalises the existing
pending-entity support to handle this case as well.

Unfortunately this does mean that almost every class definition will be
added to the pending-entity table, and almost always unnecessarily, but
I don't see a good way to avoid this.

gcc/cp/ChangeLog:

	* module.cc (depset::DB_IS_MEMBER_BIT): Rename to...
	(depset::DB_IS_PENDING_BIT): ...this.
	(depset::is_member): Remove.
	(depset::is_pending_entity): New function.
	(depset::hash::make_dependency): Mark definitions of
	namespace-scope types as maybe-pending entities.
	(depset::hash::add_class_entities): Rename DB_IS_MEMBER_BIT to
	DB_IS_PENDING_BIT.
	(depset::hash::find_dependencies): Use is_pending_entity
	instead of is_member.
	(module_state::write_pendings): Likewise; adjust comment.

gcc/testsuite/ChangeLog:

	* g++.dg/modules/inst-4_b.C: Adjust pending-entity count.
	* g++.dg/modules/member-def-1_c.C: Likewise.
	* g++.dg/modules/member-def-2_c.C: Likewise.
	* g++.dg/modules/tpl-spec-3_b.C: Likewise.
	* g++.dg/modules/tpl-spec-4_b.C: Likewise.
	* g++.dg/modules/tpl-spec-5_b.C: Likewise.
	* g++.dg/modules/class-9_a.H: New test.
	* g++.dg/modules/class-9_b.H: New test.
	* g++.dg/modules/class-9_c.C: New test.

Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
---
 gcc/cp/module.cc                              | 54 +++++++++++--------
 gcc/testsuite/g++.dg/modules/class-9_a.H      |  8 +++
 gcc/testsuite/g++.dg/modules/class-9_b.H      |  7 +++
 gcc/testsuite/g++.dg/modules/class-9_c.C      | 10 ++++
 gcc/testsuite/g++.dg/modules/inst-4_b.C       |  2 +-
 gcc/testsuite/g++.dg/modules/member-def-1_c.C |  4 +-
 gcc/testsuite/g++.dg/modules/member-def-2_c.C |  2 +-
 gcc/testsuite/g++.dg/modules/tpl-spec-3_b.C   |  3 +-
 gcc/testsuite/g++.dg/modules/tpl-spec-4_b.C   |  2 +-
 gcc/testsuite/g++.dg/modules/tpl-spec-5_b.C   |  2 +-
 10 files changed, 64 insertions(+), 30 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/modules/class-9_a.H
 create mode 100644 gcc/testsuite/g++.dg/modules/class-9_b.H
 create mode 100644 gcc/testsuite/g++.dg/modules/class-9_c.C

Comments

Jason Merrill Oct. 22, 2024, 2:53 a.m. UTC | #1
On 10/21/24 8:12 AM, Nathaniel Shead wrote:
> Bootstrapped and regtested on x86_64-pc-linux-gnu, OK for trunk?

OK.
  > -- >8 --
> 
> In some cases we can access members of a namespace-scope class without
> ever having performed name-lookup on it; this can occur when a
> forward-declaration of the class is used as a return type, for
> instance, or with PIMPL.
> 
> One possible approach would be to do name lookup in complete_type to
> force lazy loading to occur, but this seems overly expensive for a
> relatively rare case.  Instead, this patch generalises the existing
> pending-entity support to handle this case as well.
> 
> Unfortunately this does mean that almost every class definition will be
> added to the pending-entity table, and almost always unnecessarily, but
> I don't see a good way to avoid this.
> 
> gcc/cp/ChangeLog:
> 
> 	* module.cc (depset::DB_IS_MEMBER_BIT): Rename to...
> 	(depset::DB_IS_PENDING_BIT): ...this.
> 	(depset::is_member): Remove.
> 	(depset::is_pending_entity): New function.
> 	(depset::hash::make_dependency): Mark definitions of
> 	namespace-scope types as maybe-pending entities.
> 	(depset::hash::add_class_entities): Rename DB_IS_MEMBER_BIT to
> 	DB_IS_PENDING_BIT.
> 	(depset::hash::find_dependencies): Use is_pending_entity
> 	instead of is_member.
> 	(module_state::write_pendings): Likewise; adjust comment.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/modules/inst-4_b.C: Adjust pending-entity count.
> 	* g++.dg/modules/member-def-1_c.C: Likewise.
> 	* g++.dg/modules/member-def-2_c.C: Likewise.
> 	* g++.dg/modules/tpl-spec-3_b.C: Likewise.
> 	* g++.dg/modules/tpl-spec-4_b.C: Likewise.
> 	* g++.dg/modules/tpl-spec-5_b.C: Likewise.
> 	* g++.dg/modules/class-9_a.H: New test.
> 	* g++.dg/modules/class-9_b.H: New test.
> 	* g++.dg/modules/class-9_c.C: New test.
> 
> Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
> ---
>   gcc/cp/module.cc                              | 54 +++++++++++--------
>   gcc/testsuite/g++.dg/modules/class-9_a.H      |  8 +++
>   gcc/testsuite/g++.dg/modules/class-9_b.H      |  7 +++
>   gcc/testsuite/g++.dg/modules/class-9_c.C      | 10 ++++
>   gcc/testsuite/g++.dg/modules/inst-4_b.C       |  2 +-
>   gcc/testsuite/g++.dg/modules/member-def-1_c.C |  4 +-
>   gcc/testsuite/g++.dg/modules/member-def-2_c.C |  2 +-
>   gcc/testsuite/g++.dg/modules/tpl-spec-3_b.C   |  3 +-
>   gcc/testsuite/g++.dg/modules/tpl-spec-4_b.C   |  2 +-
>   gcc/testsuite/g++.dg/modules/tpl-spec-5_b.C   |  2 +-
>   10 files changed, 64 insertions(+), 30 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/modules/class-9_a.H
>   create mode 100644 gcc/testsuite/g++.dg/modules/class-9_b.H
>   create mode 100644 gcc/testsuite/g++.dg/modules/class-9_c.C
> 
> diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
> index 2dc59ce8a12..fd9b1d3bf2e 100644
> --- a/gcc/cp/module.cc
> +++ b/gcc/cp/module.cc
> @@ -2329,7 +2329,7 @@ private:
>       DB_KIND_BIT, /* Kind of the entity.  */
>       DB_KIND_BITS = EK_BITS,
>       DB_DEFN_BIT = DB_KIND_BIT + DB_KIND_BITS,
> -    DB_IS_MEMBER_BIT,		/* Is an out-of-class member.  */
> +    DB_IS_PENDING_BIT,		/* Is a maybe-pending entity.  */
>       DB_IS_INTERNAL_BIT,		/* It is an (erroneous)
>   				   internal-linkage entity.  */
>       DB_REFS_INTERNAL_BIT,	/* Refers to an internal-linkage
> @@ -2407,11 +2407,14 @@ public:
>     }
>   
>   public:
> -  /* This class-member is defined here, but the class was imported.  */
> -  bool is_member () const
> +  /* This entity might be found other than by namespace-scope lookup;
> +     see module_state::write_pendings for more details.  */
> +  bool is_pending_entity () const
>     {
> -    gcc_checking_assert (get_entity_kind () == EK_DECL);
> -    return get_flag_bit<DB_IS_MEMBER_BIT> ();
> +    return (get_entity_kind () == EK_SPECIALIZATION
> +	    || get_entity_kind () == EK_PARTIAL
> +	    || (get_entity_kind () == EK_DECL
> +		&& get_flag_bit<DB_IS_PENDING_BIT> ()));
>     }
>   public:
>     bool is_internal () const
> @@ -13031,6 +13034,18 @@ depset::hash::make_dependency (tree decl, entity_kind ek)
>   		    dep->set_flag_bit<DB_IS_INTERNAL_BIT> ();
>   		}
>   	    }
> +
> +	  /* A namespace-scope type may be declared in one module unit
> +	     and defined in another; make sure that we're found when
> +	     completing the class.  */
> +	  if (ek == EK_DECL
> +	      && !dep->is_import ()
> +	      && dep->has_defn ()
> +	      && DECL_NAMESPACE_SCOPE_P (not_tmpl)
> +	      && DECL_IMPLICIT_TYPEDEF_P (not_tmpl)
> +	      /* Anonymous types can't be forward-declared.  */
> +	      && !IDENTIFIER_ANON_P (DECL_NAME (not_tmpl)))
> +	    dep->set_flag_bit<DB_IS_PENDING_BIT> ();
>   	}
>   
>         if (!dep->is_import ())
> @@ -13383,9 +13398,9 @@ depset::hash::add_class_entities (vec<tree, va_gc> *class_members)
>         if (dep->get_entity_kind () == EK_REDIRECT)
>   	dep = dep->deps[0];
>   
> -      /* Only non-instantiations need marking as members.  */
> +      /* Only non-instantiations need marking as pendings.  */
>         if (dep->get_entity_kind () == EK_DECL)
> -	dep->set_flag_bit <DB_IS_MEMBER_BIT> ();
> +	dep->set_flag_bit <DB_IS_PENDING_BIT> ();
>       }
>   }
>   
> @@ -13711,10 +13726,7 @@ depset::hash::find_dependencies (module_state *module)
>   		  walker.mark_declaration (decl, current->has_defn ());
>   
>   		  if (!walker.is_key_order ()
> -		      && (item->get_entity_kind () == EK_SPECIALIZATION
> -			  || item->get_entity_kind () == EK_PARTIAL
> -			  || (item->get_entity_kind () == EK_DECL
> -			      && item->is_member ())))
> +		      && item->is_pending_entity ())
>   		    {
>   		      tree ns = find_pending_key (decl, nullptr);
>   		      add_namespace_context (item, ns);
> @@ -15939,15 +15951,13 @@ module_state::read_entities (unsigned count, unsigned lwm, unsigned hwm)
>      'instantiated' in one module, and it'd be nice to not have to
>      reinstantiate it in another.
>   
> -   (c) A member classes completed elsewhere.  A member class could be
> -   declared in one header and defined in another.  We need to know to
> -   load the class definition before looking in it.  This turns out to
> -   be a specific case of #b, so we can treat these the same.  But it
> -   does highlight an issue -- there could be an intermediate import
> -   between the outermost containing namespace-scope class and the
> -   innermost being-defined member class.  This is actually possible
> -   with all of these cases, so be aware -- we're not just talking of
> -   one level of import to get to the innermost namespace.
> +   (c) Classes completed elsewhere.  A class could be declared in one
> +   header and defined in another.  We need to know to load the class
> +   definition before looking in it.  It does highlight an issue --
> +   there could be an intermediate import between the outermost containing
> +   namespace-scope class and the innermost being-defined class.  This is
> +   actually possible with all of these cases, so be aware -- we're not
> +   just talking of one level of import to get to the innermost namespace.
>   
>      This gets complicated fast, it took me multiple attempts to even
>      get something remotely working.  Partially because I focussed on
> @@ -16067,9 +16077,7 @@ module_state::write_pendings (elf_out *to, vec<depset *> depsets,
>         if (d->is_import ())
>   	continue;
>   
> -      if (!(d->get_entity_kind () == depset::EK_SPECIALIZATION
> -	    || d->get_entity_kind () == depset::EK_PARTIAL
> -	    || (d->get_entity_kind () == depset::EK_DECL && d->is_member ())))
> +      if (!d->is_pending_entity ())
>   	continue;
>   
>         tree key_decl = nullptr;
> diff --git a/gcc/testsuite/g++.dg/modules/class-9_a.H b/gcc/testsuite/g++.dg/modules/class-9_a.H
> new file mode 100644
> index 00000000000..9a0bf6323f7
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/class-9_a.H
> @@ -0,0 +1,8 @@
> +// { dg-additional-options "-fmodule-header" }
> +// { dg-module-cmi {} }
> +
> +struct A;
> +A* foo();
> +
> +template <typename T> struct B;
> +template <typename T> B<T>* bar();
> diff --git a/gcc/testsuite/g++.dg/modules/class-9_b.H b/gcc/testsuite/g++.dg/modules/class-9_b.H
> new file mode 100644
> index 00000000000..931adf66081
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/class-9_b.H
> @@ -0,0 +1,7 @@
> +// { dg-additional-options "-fmodule-header -fdump-lang-module" }
> +// { dg-module-cmi {} }
> +
> +struct A { int a; };
> +template <typename T> struct B { int b; };
> +
> +// { dg-final { scan-lang-dump {Pendings 2} module } }
> diff --git a/gcc/testsuite/g++.dg/modules/class-9_c.C b/gcc/testsuite/g++.dg/modules/class-9_c.C
> new file mode 100644
> index 00000000000..de34efdae0b
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/class-9_c.C
> @@ -0,0 +1,10 @@
> +// { dg-additional-options "-fmodules-ts -fmodule-lazy" }
> +
> +import "class-9_a.H";
> +import "class-9_b.H";
> +
> +int main() {
> +  // Lazy loading should still find the definitions of A and B.
> +  int a = foo()->a;
> +  int b = bar<int>()->b;
> +}
> diff --git a/gcc/testsuite/g++.dg/modules/inst-4_b.C b/gcc/testsuite/g++.dg/modules/inst-4_b.C
> index c7b02b470bd..40f9ba976db 100644
> --- a/gcc/testsuite/g++.dg/modules/inst-4_b.C
> +++ b/gcc/testsuite/g++.dg/modules/inst-4_b.C
> @@ -9,5 +9,5 @@ int main ()
>     return 0;
>   }
>   
> -// { dg-final { scan-lang-dump {Reading 1 pending entities keyed to '::TPL'} module } }
> +// { dg-final { scan-lang-dump {Reading 2 pending entities keyed to '::TPL'} module } }
>   // { dg-final { scan-lang-dump {Read:-[0-9]*'s type spec merge key \(new\) type_decl:'::TPL'} module } }
> diff --git a/gcc/testsuite/g++.dg/modules/member-def-1_c.C b/gcc/testsuite/g++.dg/modules/member-def-1_c.C
> index d4190a84d58..fee6f4207e3 100644
> --- a/gcc/testsuite/g++.dg/modules/member-def-1_c.C
> +++ b/gcc/testsuite/g++.dg/modules/member-def-1_c.C
> @@ -11,6 +11,6 @@ export auto foo ()
>     return frob::inner ();
>   }
>   
> -// { dg-final { scan-lang-dump {Reading 1 pending entities keyed to '::frob'} module } }
> +// { dg-final { scan-lang-dump {Reading 2 pending entities keyed to '::frob'} module } }
>   // { dg-final { scan-lang-dump { Cluster members:\n  \[0\]=decl definition '::frob@foo:part1:1'\n  \[1\]=decl definition '::frob@foo:part1:1::inner@foo:part1:1'\n  \[2\]=decl declaration '::frob@foo:part1:1::inner@foo:part1:1::__dt '\n(  \[.\]=decl declaration '::frob@foo:part1:1::inner@foo:part1:1::__ct '\n)*  \[6\]=decl declaration '::frob@foo:part1:1::inner@foo:part1:1::inner@foo:part2:2'\n  \[7\]=decl declaration '::frob@foo:part1:1::frob@foo:part1:1'\n  \[8\]=decl declaration '::frob@foo:part1:1::__as_base @foo:part1:1'\n  \[9\]=binding '::frob'\n} module } }
> -// { dg-final { scan-lang-dump {Pendings 0} module } }
> +// { dg-final { scan-lang-dump {Pendings 1} module } }
> diff --git a/gcc/testsuite/g++.dg/modules/member-def-2_c.C b/gcc/testsuite/g++.dg/modules/member-def-2_c.C
> index f0a193f34ce..33b1b9fe9d2 100644
> --- a/gcc/testsuite/g++.dg/modules/member-def-2_c.C
> +++ b/gcc/testsuite/g++.dg/modules/member-def-2_c.C
> @@ -9,7 +9,7 @@ export import :part1;
>   
>   // { dg-final { scan-lang-dump { Cluster members:\n  \[0\]=decl definition '::frob@foo:part1:1'\n  \[1\]=decl declaration '::frob@foo:part1:1::frob@foo:part1:1'\n  \[2\]=decl definition '::frob@foo:part1:1::member@foo:part1:1'\n  \[3\]=decl declaration '::frob@foo:part1:1::__as_base @foo:part1:1'\n  \[4\]=binding '::frob'\n} module } }
>   // { dg-final { scan-lang-dump {Bindings 1} module } }
> -// { dg-final { scan-lang-dump {Pendings 0} module } }
> +// { dg-final { scan-lang-dump {Pendings 1} module } }
>   // { dg-final { scan-lang-dump {Read:-[0-9]*'s named merge key .matched. function_decl:'::frob@foo:part1:1::member'} module } }
>   
>   // { dg-final { scan-assembler-not {_ZN4frob6memberEv:} } }
> diff --git a/gcc/testsuite/g++.dg/modules/tpl-spec-3_b.C b/gcc/testsuite/g++.dg/modules/tpl-spec-3_b.C
> index a7105ae6759..80534ff64a5 100644
> --- a/gcc/testsuite/g++.dg/modules/tpl-spec-3_b.C
> +++ b/gcc/testsuite/g++.dg/modules/tpl-spec-3_b.C
> @@ -17,7 +17,8 @@ int main ()
>     return 0;
>   }
>   
> -// { dg-final { scan-lang-dump {Reading 1 pending entities keyed to '::frob'} module } }
> +// We read a pending for both '::frob' and '::frob::store'.
> +// { dg-final { scan-lang-dump {Reading 2 pending entities keyed to '::frob'} module } }
>   // { dg-final { scan-lang-dump-not {Reading definition function_decl '::frob@TPL:.::store@TPL:.<int>'} module } }
>   
>   // { dg-final { scan-assembler-not {_ZN4frob5storeIiEEvT_:} } }
> diff --git a/gcc/testsuite/g++.dg/modules/tpl-spec-4_b.C b/gcc/testsuite/g++.dg/modules/tpl-spec-4_b.C
> index 97aa251d3e0..ea8d014e88b 100644
> --- a/gcc/testsuite/g++.dg/modules/tpl-spec-4_b.C
> +++ b/gcc/testsuite/g++.dg/modules/tpl-spec-4_b.C
> @@ -14,4 +14,4 @@ int main ()
>     return 0;
>   }
>   
> -// { dg-final { scan-lang-dump {Reading 1 pending entities keyed to '::X'} module } }
> +// { dg-final { scan-lang-dump {Reading 2 pending entities keyed to '::X'} module } }
> diff --git a/gcc/testsuite/g++.dg/modules/tpl-spec-5_b.C b/gcc/testsuite/g++.dg/modules/tpl-spec-5_b.C
> index ff3d84c1384..300f649ac27 100644
> --- a/gcc/testsuite/g++.dg/modules/tpl-spec-5_b.C
> +++ b/gcc/testsuite/g++.dg/modules/tpl-spec-5_b.C
> @@ -14,4 +14,4 @@ int main ()
>     return 0;
>   }
>   
> -// { dg-final { scan-lang-dump {Reading 1 pending entities keyed to '::X'} module } }
> +// { dg-final { scan-lang-dump {Reading 2 pending entities keyed to '::X'} module } }
diff mbox series

Patch

diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index 2dc59ce8a12..fd9b1d3bf2e 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -2329,7 +2329,7 @@  private:
     DB_KIND_BIT, /* Kind of the entity.  */
     DB_KIND_BITS = EK_BITS,
     DB_DEFN_BIT = DB_KIND_BIT + DB_KIND_BITS,
-    DB_IS_MEMBER_BIT,		/* Is an out-of-class member.  */
+    DB_IS_PENDING_BIT,		/* Is a maybe-pending entity.  */
     DB_IS_INTERNAL_BIT,		/* It is an (erroneous)
 				   internal-linkage entity.  */
     DB_REFS_INTERNAL_BIT,	/* Refers to an internal-linkage
@@ -2407,11 +2407,14 @@  public:
   }
 
 public:
-  /* This class-member is defined here, but the class was imported.  */
-  bool is_member () const
+  /* This entity might be found other than by namespace-scope lookup;
+     see module_state::write_pendings for more details.  */
+  bool is_pending_entity () const
   {
-    gcc_checking_assert (get_entity_kind () == EK_DECL);
-    return get_flag_bit<DB_IS_MEMBER_BIT> ();
+    return (get_entity_kind () == EK_SPECIALIZATION
+	    || get_entity_kind () == EK_PARTIAL
+	    || (get_entity_kind () == EK_DECL
+		&& get_flag_bit<DB_IS_PENDING_BIT> ()));
   }
 public:
   bool is_internal () const
@@ -13031,6 +13034,18 @@  depset::hash::make_dependency (tree decl, entity_kind ek)
 		    dep->set_flag_bit<DB_IS_INTERNAL_BIT> ();
 		}
 	    }
+
+	  /* A namespace-scope type may be declared in one module unit
+	     and defined in another; make sure that we're found when
+	     completing the class.  */
+	  if (ek == EK_DECL
+	      && !dep->is_import ()
+	      && dep->has_defn ()
+	      && DECL_NAMESPACE_SCOPE_P (not_tmpl)
+	      && DECL_IMPLICIT_TYPEDEF_P (not_tmpl)
+	      /* Anonymous types can't be forward-declared.  */
+	      && !IDENTIFIER_ANON_P (DECL_NAME (not_tmpl)))
+	    dep->set_flag_bit<DB_IS_PENDING_BIT> ();
 	}
 
       if (!dep->is_import ())
@@ -13383,9 +13398,9 @@  depset::hash::add_class_entities (vec<tree, va_gc> *class_members)
       if (dep->get_entity_kind () == EK_REDIRECT)
 	dep = dep->deps[0];
 
-      /* Only non-instantiations need marking as members.  */
+      /* Only non-instantiations need marking as pendings.  */
       if (dep->get_entity_kind () == EK_DECL)
-	dep->set_flag_bit <DB_IS_MEMBER_BIT> ();
+	dep->set_flag_bit <DB_IS_PENDING_BIT> ();
     }
 }
 
@@ -13711,10 +13726,7 @@  depset::hash::find_dependencies (module_state *module)
 		  walker.mark_declaration (decl, current->has_defn ());
 
 		  if (!walker.is_key_order ()
-		      && (item->get_entity_kind () == EK_SPECIALIZATION
-			  || item->get_entity_kind () == EK_PARTIAL
-			  || (item->get_entity_kind () == EK_DECL
-			      && item->is_member ())))
+		      && item->is_pending_entity ())
 		    {
 		      tree ns = find_pending_key (decl, nullptr);
 		      add_namespace_context (item, ns);
@@ -15939,15 +15951,13 @@  module_state::read_entities (unsigned count, unsigned lwm, unsigned hwm)
    'instantiated' in one module, and it'd be nice to not have to
    reinstantiate it in another.
 
-   (c) A member classes completed elsewhere.  A member class could be
-   declared in one header and defined in another.  We need to know to
-   load the class definition before looking in it.  This turns out to
-   be a specific case of #b, so we can treat these the same.  But it
-   does highlight an issue -- there could be an intermediate import
-   between the outermost containing namespace-scope class and the
-   innermost being-defined member class.  This is actually possible
-   with all of these cases, so be aware -- we're not just talking of
-   one level of import to get to the innermost namespace.
+   (c) Classes completed elsewhere.  A class could be declared in one
+   header and defined in another.  We need to know to load the class
+   definition before looking in it.  It does highlight an issue --
+   there could be an intermediate import between the outermost containing
+   namespace-scope class and the innermost being-defined class.  This is
+   actually possible with all of these cases, so be aware -- we're not
+   just talking of one level of import to get to the innermost namespace.
 
    This gets complicated fast, it took me multiple attempts to even
    get something remotely working.  Partially because I focussed on
@@ -16067,9 +16077,7 @@  module_state::write_pendings (elf_out *to, vec<depset *> depsets,
       if (d->is_import ())
 	continue;
 
-      if (!(d->get_entity_kind () == depset::EK_SPECIALIZATION
-	    || d->get_entity_kind () == depset::EK_PARTIAL
-	    || (d->get_entity_kind () == depset::EK_DECL && d->is_member ())))
+      if (!d->is_pending_entity ())
 	continue;
 
       tree key_decl = nullptr;
diff --git a/gcc/testsuite/g++.dg/modules/class-9_a.H b/gcc/testsuite/g++.dg/modules/class-9_a.H
new file mode 100644
index 00000000000..9a0bf6323f7
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/class-9_a.H
@@ -0,0 +1,8 @@ 
+// { dg-additional-options "-fmodule-header" }
+// { dg-module-cmi {} }
+
+struct A;
+A* foo();
+
+template <typename T> struct B;
+template <typename T> B<T>* bar();
diff --git a/gcc/testsuite/g++.dg/modules/class-9_b.H b/gcc/testsuite/g++.dg/modules/class-9_b.H
new file mode 100644
index 00000000000..931adf66081
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/class-9_b.H
@@ -0,0 +1,7 @@ 
+// { dg-additional-options "-fmodule-header -fdump-lang-module" }
+// { dg-module-cmi {} }
+
+struct A { int a; };
+template <typename T> struct B { int b; };
+
+// { dg-final { scan-lang-dump {Pendings 2} module } }
diff --git a/gcc/testsuite/g++.dg/modules/class-9_c.C b/gcc/testsuite/g++.dg/modules/class-9_c.C
new file mode 100644
index 00000000000..de34efdae0b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/class-9_c.C
@@ -0,0 +1,10 @@ 
+// { dg-additional-options "-fmodules-ts -fmodule-lazy" }
+
+import "class-9_a.H";
+import "class-9_b.H";
+
+int main() {
+  // Lazy loading should still find the definitions of A and B.
+  int a = foo()->a;
+  int b = bar<int>()->b;
+}
diff --git a/gcc/testsuite/g++.dg/modules/inst-4_b.C b/gcc/testsuite/g++.dg/modules/inst-4_b.C
index c7b02b470bd..40f9ba976db 100644
--- a/gcc/testsuite/g++.dg/modules/inst-4_b.C
+++ b/gcc/testsuite/g++.dg/modules/inst-4_b.C
@@ -9,5 +9,5 @@  int main ()
   return 0;
 }
 
-// { dg-final { scan-lang-dump {Reading 1 pending entities keyed to '::TPL'} module } }
+// { dg-final { scan-lang-dump {Reading 2 pending entities keyed to '::TPL'} module } }
 // { dg-final { scan-lang-dump {Read:-[0-9]*'s type spec merge key \(new\) type_decl:'::TPL'} module } }
diff --git a/gcc/testsuite/g++.dg/modules/member-def-1_c.C b/gcc/testsuite/g++.dg/modules/member-def-1_c.C
index d4190a84d58..fee6f4207e3 100644
--- a/gcc/testsuite/g++.dg/modules/member-def-1_c.C
+++ b/gcc/testsuite/g++.dg/modules/member-def-1_c.C
@@ -11,6 +11,6 @@  export auto foo ()
   return frob::inner ();
 }
 
-// { dg-final { scan-lang-dump {Reading 1 pending entities keyed to '::frob'} module } }
+// { dg-final { scan-lang-dump {Reading 2 pending entities keyed to '::frob'} module } }
 // { dg-final { scan-lang-dump { Cluster members:\n  \[0\]=decl definition '::frob@foo:part1:1'\n  \[1\]=decl definition '::frob@foo:part1:1::inner@foo:part1:1'\n  \[2\]=decl declaration '::frob@foo:part1:1::inner@foo:part1:1::__dt '\n(  \[.\]=decl declaration '::frob@foo:part1:1::inner@foo:part1:1::__ct '\n)*  \[6\]=decl declaration '::frob@foo:part1:1::inner@foo:part1:1::inner@foo:part2:2'\n  \[7\]=decl declaration '::frob@foo:part1:1::frob@foo:part1:1'\n  \[8\]=decl declaration '::frob@foo:part1:1::__as_base @foo:part1:1'\n  \[9\]=binding '::frob'\n} module } }
-// { dg-final { scan-lang-dump {Pendings 0} module } }
+// { dg-final { scan-lang-dump {Pendings 1} module } }
diff --git a/gcc/testsuite/g++.dg/modules/member-def-2_c.C b/gcc/testsuite/g++.dg/modules/member-def-2_c.C
index f0a193f34ce..33b1b9fe9d2 100644
--- a/gcc/testsuite/g++.dg/modules/member-def-2_c.C
+++ b/gcc/testsuite/g++.dg/modules/member-def-2_c.C
@@ -9,7 +9,7 @@  export import :part1;
 
 // { dg-final { scan-lang-dump { Cluster members:\n  \[0\]=decl definition '::frob@foo:part1:1'\n  \[1\]=decl declaration '::frob@foo:part1:1::frob@foo:part1:1'\n  \[2\]=decl definition '::frob@foo:part1:1::member@foo:part1:1'\n  \[3\]=decl declaration '::frob@foo:part1:1::__as_base @foo:part1:1'\n  \[4\]=binding '::frob'\n} module } }
 // { dg-final { scan-lang-dump {Bindings 1} module } }
-// { dg-final { scan-lang-dump {Pendings 0} module } }
+// { dg-final { scan-lang-dump {Pendings 1} module } }
 // { dg-final { scan-lang-dump {Read:-[0-9]*'s named merge key .matched. function_decl:'::frob@foo:part1:1::member'} module } }
 
 // { dg-final { scan-assembler-not {_ZN4frob6memberEv:} } }
diff --git a/gcc/testsuite/g++.dg/modules/tpl-spec-3_b.C b/gcc/testsuite/g++.dg/modules/tpl-spec-3_b.C
index a7105ae6759..80534ff64a5 100644
--- a/gcc/testsuite/g++.dg/modules/tpl-spec-3_b.C
+++ b/gcc/testsuite/g++.dg/modules/tpl-spec-3_b.C
@@ -17,7 +17,8 @@  int main ()
   return 0;
 }
 
-// { dg-final { scan-lang-dump {Reading 1 pending entities keyed to '::frob'} module } }
+// We read a pending for both '::frob' and '::frob::store'.
+// { dg-final { scan-lang-dump {Reading 2 pending entities keyed to '::frob'} module } }
 // { dg-final { scan-lang-dump-not {Reading definition function_decl '::frob@TPL:.::store@TPL:.<int>'} module } }
 
 // { dg-final { scan-assembler-not {_ZN4frob5storeIiEEvT_:} } }
diff --git a/gcc/testsuite/g++.dg/modules/tpl-spec-4_b.C b/gcc/testsuite/g++.dg/modules/tpl-spec-4_b.C
index 97aa251d3e0..ea8d014e88b 100644
--- a/gcc/testsuite/g++.dg/modules/tpl-spec-4_b.C
+++ b/gcc/testsuite/g++.dg/modules/tpl-spec-4_b.C
@@ -14,4 +14,4 @@  int main ()
   return 0;
 }
 
-// { dg-final { scan-lang-dump {Reading 1 pending entities keyed to '::X'} module } }
+// { dg-final { scan-lang-dump {Reading 2 pending entities keyed to '::X'} module } }
diff --git a/gcc/testsuite/g++.dg/modules/tpl-spec-5_b.C b/gcc/testsuite/g++.dg/modules/tpl-spec-5_b.C
index ff3d84c1384..300f649ac27 100644
--- a/gcc/testsuite/g++.dg/modules/tpl-spec-5_b.C
+++ b/gcc/testsuite/g++.dg/modules/tpl-spec-5_b.C
@@ -14,4 +14,4 @@  int main ()
   return 0;
 }
 
-// { dg-final { scan-lang-dump {Reading 1 pending entities keyed to '::X'} module } }
+// { dg-final { scan-lang-dump {Reading 2 pending entities keyed to '::X'} module } }