[v2,1/6] c++/modules: Detect exposures of TU-local entities

Message ID 66f64969.170a0220.2e3b0a.3c61@mx.google.com
State New
Headers
Series c++/modules: Implement P1815 "Translation-unit-local entities" |

Commit Message

Nathaniel Shead Sept. 27, 2024, 5:57 a.m. UTC
  I feel like there should be a way to make use of LAMBDA_TYPE_EXTRA_SCOPE to
avoid the need for the new TYPE_DEFINED_IN_INITIALIZER_P flag, perhaps once
something like my patch here[1] is accepted (but with further embellishments
for concepts, probably), but I wasn't able to work it out.  Since currently as
far as I'm aware only lambdas can satisfy being a type with no name defined in
an 'initializer' this does seem a little overkill but I've applied it to all
class types just in case.

[1]: https://gcc.gnu.org/pipermail/gcc-patches/2024-September/662393.html

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

-- >8 --

Currently, the modules streaming code implements some checks for
declarations in the CMI that reference (some kinds of) internal-linkage
entities, and errors if so.  This patch expands on that support to
implement the logic for exposures of TU-local entities as defined in
[basic.link] since P1815.

This will cause some code that previously errored in modules to start
compiling; for instance, template specialisations of internal linkage
functions.

However, some code that previously appeared valid will with this patch
no longer compile, notably some kinds of usages of internal linkage
functions included from the GMF.  This appears to be related to P2808
and FR-025, however as yet there doesn't appear to be consensus for
changing these rules so I've implemented them as-is.

This patch leaves a couple of things out.  In particular, a couple of
the rules for what is a TU-local entity currently seem to me to be
redundant; I've left them as FIXMEs to be handled once I can find
testcases that aren't adequately supported by the other logic here.

Additionally, there are some exceptions for when naming a TU-local
entity is not always an exposure; I've left support for this to a
follow-up patch for easier review, as it has broader implications for
streaming.

Finally, this patch makes a couple of small adjustments to the modules
streaming logic to prune any leftover TU-local deps (that aren't
erroneous exposures).  This is required for this patch to ensure that
later stages don't get confused by any leftover TU-local entities
floating around.

gcc/cp/ChangeLog:

	* cp-tree.h (TYPE_DEPENDENT_P_VALID): Fix whitespace.
	(TYPE_DEFINED_IN_INITIALIZER_P): New accessor.
	* module.cc (DB_IS_INTERNAL_BIT): Rename to...
	(DB_TU_LOCAL_BIT): ...this.
	(DB_REFS_INTERNAL_BIT): Rename to...
	(DB_EXPOSURE_BIT): ...this.
	(depset::hash::is_internal): Rename to...
	(depset::hash::is_tu_local): ...this.
	(depset::hash::refs_internal): Rename to...
	(depset::hash::is_exposure): ...this.
	(depset::hash::is_tu_local_entity): New function.
	(depset::hash::has_tu_local_tmpl_arg): New function.
	(depset::hash::is_tu_local_value): New function.
	(depset::hash::make_dependency): Check for TU-local entities.
	(depset::hash::add_dependency): Make current an exposure
	whenever it references a TU-local entity.
	(depset::hash::add_binding_entity): Don't create bindings for
	any TU-local entity.
	(depset::hash::finalize_dependencies): Rename flags and adjust
	diagnostic messages to report exposures of TU-local entities.
	(depset::tarjan::connect): Don't include any TU-local depsets.
	(depset::hash::connect): Likewise.
	(trees_out::core_bools): Stream TYPE_LANG_FLAG_7.
	(trees_in::core_bools): Read it.
	* parser.h (struct cp_parser::in_initializer_p): New flag.
	* parser.cc (cp_debug_parser): Print the new flag.
	(cp_parser_new): Set the new flag to false.
	(cp_parser_lambda_expression): Mark whether the lambda was
	defined in an initializer.
	(cp_parser_initializer): Set the new flag to true while parsing.
	(cp_parser_class_head): Mark whether the class was defined in an
	initializer.
	(cp_parser_concept_definition): Set the new flag to true while
	parsing.

gcc/testsuite/ChangeLog:

	* g++.dg/modules/block-decl-2.C: Adjust messages.
	* g++.dg/modules/internal-1.C: Adjust messages, remove XFAILs.
	* g++.dg/modules/linkage-2.C: Adjust messages, remove XFAILS.
	* g++.dg/modules/internal-3.C: New test.
	* g++.dg/modules/internal-4_a.H: New test.
	* g++.dg/modules/internal-4_a.C: New test.

Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
---
 gcc/cp/cp-tree.h                            |   7 +-
 gcc/cp/module.cc                            | 382 +++++++++++++++++---
 gcc/cp/parser.cc                            |  16 +
 gcc/cp/parser.h                             |   3 +
 gcc/testsuite/g++.dg/modules/block-decl-2.C |   2 +-
 gcc/testsuite/g++.dg/modules/internal-1.C   |  15 +-
 gcc/testsuite/g++.dg/modules/internal-3.C   |  18 +
 gcc/testsuite/g++.dg/modules/internal-4_a.H |   4 +
 gcc/testsuite/g++.dg/modules/internal-4_b.C | 124 +++++++
 gcc/testsuite/g++.dg/modules/linkage-2.C    |   5 +-
 10 files changed, 505 insertions(+), 71 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/modules/internal-3.C
 create mode 100644 gcc/testsuite/g++.dg/modules/internal-4_a.H
 create mode 100644 gcc/testsuite/g++.dg/modules/internal-4_b.C
  

Patch

diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 7c438eca16d..cfaf82b018c 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -534,6 +534,7 @@  extern GTY(()) tree cp_global_trees[CPTI_MAX];
       AUTO_IS_DECLTYPE (in TEMPLATE_TYPE_PARM)
       TEMPLATE_TEMPLATE_PARM_SIMPLE_P (in TEMPLATE_TEMPLATE_PARM)
    6: TYPE_DEPENDENT_P_VALID
+   7: TYPE_DEFINED_IN_INITIALIZER_P
 
    Usage of DECL_LANG_FLAG_?:
    0: DECL_TEMPLATE_PARM_P (in PARM_DECL, CONST_DECL, TYPE_DECL, or TEMPLATE_DECL)
@@ -2292,7 +2293,11 @@  enum languages { lang_c, lang_cplusplus };
 
 /* True if dependent_type_p has been called for this type, with the
    result that TYPE_DEPENDENT_P is valid.  */
-#define TYPE_DEPENDENT_P_VALID(NODE) TYPE_LANG_FLAG_6(NODE)
+#define TYPE_DEPENDENT_P_VALID(NODE) TYPE_LANG_FLAG_6 (NODE)
+
+/* True if this type was defined in an initializer.  Used for determining
+   whether an entity is TU-local.  */
+#define TYPE_DEFINED_IN_INITIALIZER_P(NODE) TYPE_LANG_FLAG_7 (NODE)
 
 /* Nonzero if this type is const-qualified.  */
 #define CP_TYPE_CONST_P(NODE)				\
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index 65b37b4b554..570320ea627 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -2330,10 +2330,8 @@  private:
     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_INTERNAL_BIT,		/* It is an (erroneous)
-				   internal-linkage entity.  */
-    DB_REFS_INTERNAL_BIT,	/* Refers to an internal-linkage
-				   entity. */
+    DB_TU_LOCAL_BIT,		/* It is a TU-local entity.  */
+    DB_EXPOSURE_BIT,		/* Exposes a TU-local entity.  */
     DB_IMPORTED_BIT,		/* An imported entity.  */
     DB_UNREACHED_BIT,		/* A yet-to-be reached entity.  */
     DB_HIDDEN_BIT,		/* A hidden binding.  */
@@ -2414,13 +2412,13 @@  public:
     return get_flag_bit<DB_IS_MEMBER_BIT> ();
   }
 public:
-  bool is_internal () const
+  bool is_tu_local () const
   {
-    return get_flag_bit<DB_IS_INTERNAL_BIT> ();
+    return get_flag_bit<DB_TU_LOCAL_BIT> ();
   }
-  bool refs_internal () const
+  bool is_exposure () const
   {
-    return get_flag_bit<DB_REFS_INTERNAL_BIT> ();
+    return get_flag_bit<DB_EXPOSURE_BIT> ();
   }
   bool is_import () const
   {
@@ -2580,6 +2578,11 @@  public:
     depset *add_dependency (tree decl, entity_kind);
     void add_namespace_context (depset *, tree ns);
 
+  private:
+    bool has_tu_local_tmpl_arg (tree decl, tree args, bool explain);
+    bool is_tu_local_entity (tree decl, bool explain = false);
+    bool is_tu_local_value (tree decl, tree expr, bool explain = false);
+
   private:
     static bool add_binding_entity (tree, WMB_Flags, void *);
 
@@ -5404,6 +5407,7 @@  trees_out::core_bools (tree t, bits_out& bits)
       WB (t->type_common.lang_flag_4);
       WB (t->type_common.lang_flag_5);
       WB (t->type_common.lang_flag_6);
+      WB (t->type_common.lang_flag_7);
       WB (t->type_common.typeless_storage);
     }
 
@@ -5605,6 +5609,7 @@  trees_in::core_bools (tree t, bits_in& bits)
       RB (t->type_common.lang_flag_4);
       RB (t->type_common.lang_flag_5);
       RB (t->type_common.lang_flag_6);
+      RB (t->type_common.lang_flag_7);
       RB (t->type_common.typeless_storage);
     }
 
@@ -12886,6 +12891,246 @@  depset::hash::find_binding (tree ctx, tree name)
   return slot ? *slot : NULL;
 }
 
+/* Returns true if DECL is a TU-local entity, as defined by [basic.link].
+   If EXPLAIN is true, emit an informative note about why DECL is TU-local.  */
+
+bool
+depset::hash::is_tu_local_entity (tree decl, bool explain/*=false*/)
+{
+  gcc_checking_assert (DECL_P (decl));
+
+  /* An explicit type alias is not an entity, and so is never TU-local.  */
+  if (TREE_CODE (decl) == TYPE_DECL
+      && !DECL_IMPLICIT_TYPEDEF_P (decl)
+      && !DECL_SELF_REFERENCE_P (decl))
+    return false;
+
+  location_t loc = DECL_SOURCE_LOCATION (decl);
+  tree type = TREE_TYPE (decl);
+
+  /* Check specializations first for slightly better explanations.  */
+  int use_tpl = -1;
+  tree ti = node_template_info (decl, use_tpl);
+  if (use_tpl > 0 && TREE_CODE (TI_TEMPLATE (ti)) == TEMPLATE_DECL)
+    {
+      /* A specialization of a TU-local template.  */
+      tree tmpl = TI_TEMPLATE (ti);
+      if (is_tu_local_entity (tmpl))
+	{
+	  if (explain)
+	    {
+	      inform (loc, "%qD is a specialization of TU-local template %qD",
+		      decl, tmpl);
+	      is_tu_local_entity (tmpl, /*explain=*/true);
+	    }
+	  return true;
+	}
+
+      /* A specialization of a template with any TU-local template argument.  */
+      if (has_tu_local_tmpl_arg (decl, TI_ARGS (ti), explain))
+	return true;
+
+      /* FIXME A specialization of a template whose (possibly instantiated)
+	 declaration is an exposure.  This should always be covered by the
+	 above cases??  */
+    }
+
+  /* A type, function, variable, or template with internal linkage.  */
+  linkage_kind kind = decl_linkage (decl);
+  if (kind == lk_internal
+      /* But although weakrefs are marked static, don't consider them
+	 to be TU-local.  */
+      && !lookup_attribute ("weakref", DECL_ATTRIBUTES (decl)))
+    {
+      if (explain)
+	inform (loc, "%qD declared with internal linkage", decl);
+      return true;
+    }
+
+  /* Does not have a name with linkage and is declared, or introduced by a
+     lambda-expression, within the definition of a TU-local entity.  */
+  if (kind == lk_none)
+    {
+      tree ctx = CP_DECL_CONTEXT (decl);
+      if (LAMBDA_TYPE_P (type))
+	if (tree extra = LAMBDA_TYPE_EXTRA_SCOPE (type))
+	  ctx = extra;
+
+      if (TREE_CODE (ctx) == NAMESPACE_DECL)
+	{
+	  if (!TREE_PUBLIC (ctx))
+	    {
+	      if (explain)
+		inform (loc, "%qD has no linkage and is declared in an "
+			"anonymous namespace", decl);
+	      return true;
+	    }
+	}
+      else if (TYPE_P (ctx))
+	{
+	  tree ctx_decl = TYPE_MAIN_DECL (ctx);
+	  if (is_tu_local_entity (ctx_decl))
+	    {
+	      if (explain)
+		{
+		  inform (loc, "%qD has no linkage and is declared within "
+			  "TU-local entity %qT", decl, ctx);
+		  is_tu_local_entity (ctx_decl, /*explain=*/true);
+		}
+	      return true;
+	    }
+	}
+      else if (is_tu_local_entity (ctx))
+	{
+	  if (explain)
+	    {
+	      inform (loc, "%qD has no linkage and is declared within "
+		      "TU-local entity %qD", decl, ctx);
+	      is_tu_local_entity (ctx, /*explain=*/true);
+	    }
+	  return true;
+	}
+    }
+
+  /* A type with no name that is defined outside a class-specifier, function
+     body, or initializer; or is introduced by a defining-type-specifier that
+     is used to declare only TU-local entities.
+
+     We consider types with names for linkage purposes as having names, since
+     these aren't really TU-local, and also consider constraint-expressions
+     as initializers.  */
+  if (TREE_CODE (decl) == TYPE_DECL
+      && TYPE_ANON_P (type)
+      && !DECL_SELF_REFERENCE_P (decl)
+      /* An enum with an enumerator name for linkage.  */
+      && !(UNSCOPED_ENUM_P (type) && TYPE_VALUES (type)))
+    {
+      tree main_decl = TYPE_MAIN_DECL (type);
+      if (!TYPE_DEFINED_IN_INITIALIZER_P (type)
+	  && !DECL_CLASS_SCOPE_P (main_decl)
+	  && !decl_function_context (main_decl))
+	{
+	  if (explain)
+	    inform (loc, "%qT has no name and is not defined within a class, "
+		    "function, or initializer", type);
+	  return true;
+	}
+
+      // FIXME introduced by a defining-type-specifier only declaring TU-local
+      // entities; does this refer to e.g. 'static struct {} a;"?  I can't
+      // think of any cases where this isn't covered by earlier cases.  */
+    }
+
+  return false;
+}
+
+/* Helper for is_tu_local_entity.  Returns true if one of the ARGS of
+   DECL is TU-local.  Emits an explanation if EXPLAIN is true.  */
+
+bool
+depset::hash::has_tu_local_tmpl_arg (tree decl, tree args, bool explain)
+{
+  if (!args || TREE_CODE (args) != TREE_VEC)
+    return false;
+
+  for (tree a : tree_vec_range (args))
+    {
+      if (TREE_CODE (a) == TREE_VEC)
+	{
+	  if (has_tu_local_tmpl_arg (decl, a, explain))
+	    return true;
+	}
+      else if (!WILDCARD_TYPE_P (a))
+	{
+	  if (DECL_P (a) && is_tu_local_entity (a))
+	    {
+	      if (explain)
+		{
+		  inform (DECL_SOURCE_LOCATION (decl),
+			  "%qD has TU-local template argument %qD",
+			  decl, a);
+		  is_tu_local_entity (a, /*explain=*/true);
+		}
+	      return true;
+	    }
+
+	  if (TYPE_P (a) && TYPE_NAME (a) && is_tu_local_entity (TYPE_NAME (a)))
+	    {
+	      if (explain)
+		{
+		  inform (DECL_SOURCE_LOCATION (decl),
+			  "%qD has TU-local template argument %qT",
+			  decl, a);
+		  is_tu_local_entity (TYPE_NAME (a), /*explain=*/true);
+		}
+	      return true;
+	    }
+
+	  if (EXPR_P (a) && is_tu_local_value (decl, a, explain))
+	    return true;
+	}
+    }
+
+  return false;
+}
+
+/* Returns true if EXPR (part of the initializer for DECL) is a TU-local value
+   or object.  Emits an explanation if EXPLAIN is true.  */
+
+bool
+depset::hash::is_tu_local_value (tree decl, tree expr, bool explain)
+{
+  if (!expr)
+    return false;
+
+  tree e = expr;
+  STRIP_ANY_LOCATION_WRAPPER (e);
+  STRIP_NOPS (e);
+  if (TREE_CODE (e) == TARGET_EXPR)
+    e = TARGET_EXPR_INITIAL (e);
+  if (!e)
+    return false;
+
+  /* It is, or is a pointer to, a TU-local function or the object associated
+     with a TU-local variable.  */
+  tree object = NULL_TREE;
+  if (TREE_CODE (e) == ADDR_EXPR)
+    object = TREE_OPERAND (e, 0);
+  else if (TREE_CODE (e) == PTRMEM_CST)
+    object = PTRMEM_CST_MEMBER (e);
+  else if (VAR_OR_FUNCTION_DECL_P (e))
+    object = e;
+
+  if (object
+      && VAR_OR_FUNCTION_DECL_P (object)
+      && is_tu_local_entity (object))
+    {
+      if (explain)
+	{
+	  /* We've lost a lot of location information by the time we get here,
+	     so let's just do our best effort.  */
+	  auto loc = cp_expr_loc_or_loc (expr, DECL_SOURCE_LOCATION (decl));
+	  if (VAR_P (object))
+	    inform (loc, "%qD refers to TU-local object %qD", decl, object);
+	  else
+	    inform (loc, "%qD refers to TU-local function %qD", decl, object);
+	  is_tu_local_entity (object, true);
+	}
+      return true;
+    }
+
+  /* It is an object of class or array type and any of its subobjects or
+     any of the objects or functions to which its non-static data members
+     of reference type refer is TU-local and is usable in constant
+     expressions.  */
+  if (TREE_CODE (e) == CONSTRUCTOR && AGGREGATE_TYPE_P (TREE_TYPE (e)))
+    for (auto& f : CONSTRUCTOR_ELTS (e))
+      if (is_tu_local_value (decl, f.value, explain))
+	return true;
+
+  return false;
+}
+
 /* DECL is a newly discovered dependency.  Create the depset, if it
    doesn't already exist.  Add it to the worklist if so.
 
@@ -12992,6 +13237,7 @@  depset::hash::make_dependency (tree decl, entity_kind ek)
       if (ek != EK_USING)
 	{
 	  tree not_tmpl = STRIP_TEMPLATE (decl);
+	  bool imported_from_module_p = false;
 
 	  if (DECL_LANG_SPECIFIC (not_tmpl)
 	      && DECL_MODULE_IMPORT_P (not_tmpl))
@@ -13007,28 +13253,36 @@  depset::hash::make_dependency (tree decl, entity_kind ek)
 		  dep->cluster = index - from->entity_lwm;
 		  dep->section = from->remap;
 		  dep->set_flag_bit<DB_IMPORTED_BIT> ();
+
+		  if (!from->is_header ())
+		    imported_from_module_p = true;
 		}
 	    }
 
-	  if (ek == EK_DECL
-	      && !dep->is_import ()
-	      && TREE_CODE (CP_DECL_CONTEXT (decl)) == NAMESPACE_DECL
-	      && !(TREE_CODE (decl) == TEMPLATE_DECL
-		   && DECL_UNINSTANTIATED_TEMPLATE_FRIEND_P (decl)))
+	  /* Check for TU-local entities.  This is unnecessary in header
+	     units because we can export internal-linkage decls, and
+	     no declarations are exposures.  Similarly, if the decl was
+	     imported from a non-header module we know it cannot have
+	     been TU-local.  */
+	  if (!header_module_p () && !imported_from_module_p)
 	    {
-	      tree ctx = CP_DECL_CONTEXT (decl);
+	      if (is_tu_local_entity (decl))
+		dep->set_flag_bit<DB_TU_LOCAL_BIT> ();
 
-	      if (!TREE_PUBLIC (ctx))
-		/* Member of internal namespace.  */
-		dep->set_flag_bit<DB_IS_INTERNAL_BIT> ();
-	      else if (VAR_OR_FUNCTION_DECL_P (not_tmpl)
-		       && DECL_THIS_STATIC (not_tmpl))
+	      if (VAR_P (decl)
+		  && decl_maybe_constant_var_p (decl)
+		  && is_tu_local_value (decl, DECL_INITIAL (decl)))
 		{
-		  /* An internal decl.  This is ok in a GM entity.  */
-		  if (!(header_module_p ()
-			|| !DECL_LANG_SPECIFIC (not_tmpl)
-			|| !DECL_MODULE_PURVIEW_P (not_tmpl)))
-		    dep->set_flag_bit<DB_IS_INTERNAL_BIT> ();
+		  /* A potentially-constant variable initialized to a TU-local
+		     value is not usable in constant expressions within other
+		     translation units.  We can achieve this by simply not
+		     streaming the definition in such cases.  */
+		  dep->clear_flag_bit<DB_DEFN_BIT> ();
+
+		  if (DECL_DECLARED_CONSTEXPR_P (decl))
+		    /* Also, a constexpr variable initialized to a TU-local
+		       value is an exposure.  */
+		    dep->set_flag_bit<DB_EXPOSURE_BIT> ();
 		}
 	    }
 	}
@@ -13055,8 +13309,8 @@  depset::hash::add_dependency (depset *dep)
   gcc_checking_assert (current && !is_key_order ());
   current->deps.safe_push (dep);
 
-  if (dep->is_internal () && !current->is_internal ())
-    current->set_flag_bit<DB_REFS_INTERNAL_BIT> ();
+  if (dep->is_tu_local ())
+    current->set_flag_bit<DB_EXPOSURE_BIT> ();
 
   if (current->get_entity_kind () == EK_USING
       && DECL_IMPLICIT_TYPEDEF_P (dep->get_entity ())
@@ -13164,13 +13418,9 @@  depset::hash::add_binding_entity (tree decl, WMB_Flags flags, void *data_)
 	/* Ignore entities not within the module purview.  */
 	return false;
 
-      if (VAR_OR_FUNCTION_DECL_P (inner)
-	  && DECL_THIS_STATIC (inner))
-	{
-	  if (!header_module_p ())
-	    /* Ignore internal-linkage entitites.  */
-	    return false;
-	}
+      if (!header_module_p () && data->hash->is_tu_local_entity (decl))
+	/* Ignore TU-local entitites.  */
+	return false;
 
       if ((TREE_CODE (decl) == VAR_DECL
 	   || TREE_CODE (decl) == TYPE_DECL)
@@ -13869,10 +14119,8 @@  bool
 depset::hash::finalize_dependencies ()
 {
   bool ok = true;
-  depset::hash::iterator end (this->end ());
-  for (depset::hash::iterator iter (begin ()); iter != end; ++iter)
+  for (depset *dep : *this)
     {
-      depset *dep = *iter;
       if (dep->is_binding ())
 	{
 	  /* Keep the containing namespace dep first.  */
@@ -13885,23 +14133,41 @@  depset::hash::finalize_dependencies ()
 	    gcc_qsort (&dep->deps[1], dep->deps.length () - 1,
 		       sizeof (dep->deps[1]), binding_cmp);
 	}
-      else if (dep->refs_internal ())
+      else if (dep->is_exposure () && !dep->is_tu_local ())
 	{
-	  for (unsigned ix = dep->deps.length (); ix--;)
+	  ok = false;
+	  bool explained = false;
+	  tree decl = dep->get_entity ();
+
+	  for (depset *rdep : dep->deps)
+	    if (!rdep->is_binding () && rdep->is_tu_local ())
+	      {
+		// FIXME:QOI Better location information?  We're
+		// losing, so it doesn't matter about efficiency
+		tree exposed = rdep->get_entity ();
+		auto_diagnostic_group d;
+		error_at (DECL_SOURCE_LOCATION (decl),
+			  "%qD exposes TU-local entity %qD", decl, exposed);
+		bool informed = is_tu_local_entity (exposed, /*explain=*/true);
+		gcc_checking_assert (informed);
+		explained = true;
+		break;
+	      }
+
+	  if (!explained && VAR_P (decl) && DECL_DECLARED_CONSTEXPR_P (decl))
 	    {
-	      depset *rdep = dep->deps[ix];
-	      if (rdep->is_internal ())
-		{
-		  // FIXME:QOI Better location information?  We're
-		  // losing, so it doesn't matter about efficiency
-		  tree decl = dep->get_entity ();
-		  error_at (DECL_SOURCE_LOCATION (decl),
-			    "%q#D references internal linkage entity %q#D",
-			    decl, rdep->get_entity ());
-		  break;
-		}
+	      auto_diagnostic_group d;
+	      error_at (DECL_SOURCE_LOCATION (decl),
+			"%qD is declared %<constexpr%> and is initialized to "
+			"a TU-local value", decl);
+	      bool informed = is_tu_local_value (decl, DECL_INITIAL (decl),
+						 /*explain=*/true);
+	      gcc_checking_assert (informed);
+	      explained = true;
 	    }
-	  ok = false;
+
+	  /* We should have emitted an error above.  */
+	  gcc_checking_assert (explained);
 	}
     }
 
@@ -13925,7 +14191,9 @@  void
 depset::tarjan::connect (depset *v)
 {
   gcc_checking_assert (v->is_binding ()
-		       || !(v->is_unreached () || v->is_import ()));
+		       || !(v->is_tu_local ()
+			    || v->is_unreached ()
+			    || v->is_import ()));
 
   v->cluster = v->section = ++index;
   stack.safe_push (v);
@@ -13935,7 +14203,8 @@  depset::tarjan::connect (depset *v)
     {
       depset *dep = v->deps[ix];
 
-      if (dep->is_binding () || !dep->is_import ())
+      if (dep->is_binding ()
+	  || !(dep->is_import () || dep->is_tu_local ()))
 	{
 	  unsigned lwm = dep->cluster;
 
@@ -14138,14 +14407,12 @@  depset::hash::connect ()
   tarjan connector (size ());
   vec<depset *> deps;
   deps.create (size ());
-  iterator end (this->end ());
-  for (iterator iter (begin ()); iter != end; ++iter)
+  for (depset *item : *this)
     {
-      depset *item = *iter;
-
       entity_kind kind = item->get_entity_kind ();
       if (kind == EK_BINDING
 	  || !(kind == EK_REDIRECT
+	       || item->is_tu_local ()
 	       || item->is_unreached ()
 	       || item->is_import ()))
 	deps.quick_push (item);
@@ -18407,7 +18674,8 @@  module_state::write_begin (elf_out *to, cpp_reader *reader,
   note_defs = note_defs_table_t::create_ggc (1000);
 #endif
 
-  /* Determine Strongy Connected Components.  */
+  /* Determine Strongy Connected Components.  This will also strip any
+     unnecessary dependencies on imported or TU-local entities.  */
   vec<depset *> sccs = table.connect ();
 
   vec_alloc (ool, modules->length ());
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index f50534f5f39..6519d9e9061 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -564,6 +564,8 @@  cp_debug_parser (FILE *file, cp_parser *parser)
 			      parser->in_unbraced_export_declaration_p);
   cp_debug_print_flag (file, "Parsing a declarator",
 			      parser->in_declarator_p);
+  cp_debug_print_flag (file, "Parsing an initializer",
+			      parser->in_initializer_p);
   cp_debug_print_flag (file, "In template argument list",
 			      parser->in_template_argument_list_p);
   cp_debug_print_flag (file, "Parsing an iteration statement",
@@ -4455,6 +4457,9 @@  cp_parser_new (cp_lexer *lexer)
   /* We are not processing a declarator.  */
   parser->in_declarator_p = false;
 
+  /* We are not processing an initializer.  */
+  parser->in_initializer_p = false;
+
   /* We are not processing a template-argument-list.  */
   parser->in_template_argument_list_p = false;
 
@@ -11426,6 +11431,7 @@  cp_parser_lambda_expression (cp_parser* parser)
 
   record_lambda_scope (lambda_expr);
   record_lambda_scope_discriminator (lambda_expr);
+  TYPE_DEFINED_IN_INITIALIZER_P (type) = parser->in_initializer_p;
 
   /* Do this again now that LAMBDA_EXPR_EXTRA_SCOPE is set.  */
   determine_visibility (TYPE_NAME (type));
@@ -26347,6 +26353,9 @@  cp_parser_initializer (cp_parser *parser, bool *is_direct_init /*=nullptr*/,
   if (non_constant_p)
     *non_constant_p = false;
 
+  bool saved_in_initializer_p = parser->in_initializer_p;
+  parser->in_initializer_p = true;
+
   if (token->type == CPP_EQ)
     {
       /* Consume the `='.  */
@@ -26383,6 +26392,8 @@  cp_parser_initializer (cp_parser *parser, bool *is_direct_init /*=nullptr*/,
   if (!subexpression_p && check_for_bare_parameter_packs (init))
     init = error_mark_node;
 
+  parser->in_initializer_p = saved_in_initializer_p;
+
   return init;
 }
 
@@ -27953,6 +27964,8 @@  cp_parser_class_head (cp_parser* parser,
     }
   else if (type == error_mark_node)
     type = NULL_TREE;
+  else
+    TYPE_DEFINED_IN_INITIALIZER_P (type) = parser->in_initializer_p;
 
   if (type)
     {
@@ -31383,6 +31396,8 @@  cp_parser_concept_definition (cp_parser *parser)
     }
 
   processing_constraint_expression_sentinel parsing_constraint;
+  parser->in_initializer_p = true;
+
   tree init = cp_parser_constraint_expression (parser);
   if (init == error_mark_node)
     cp_parser_skip_to_end_of_statement (parser);
@@ -31391,6 +31406,7 @@  cp_parser_concept_definition (cp_parser *parser)
      but continue as if it were.  */
   cp_parser_consume_semicolon_at_end_of_statement (parser);
 
+  parser->in_initializer_p = false;
   return finish_concept_definition (id, init, attrs);
 }
 
diff --git a/gcc/cp/parser.h b/gcc/cp/parser.h
index 09b356e5e73..ee8c4f9001e 100644
--- a/gcc/cp/parser.h
+++ b/gcc/cp/parser.h
@@ -323,6 +323,9 @@  struct GTY(()) cp_parser {
      direct-declarator.  */
   bool in_declarator_p;
 
+  /* TRUE if we are parsing an initializer.  */
+  bool in_initializer_p;
+
   /* TRUE if we are presently parsing a template-argument-list.  */
   bool in_template_argument_list_p;
 
diff --git a/gcc/testsuite/g++.dg/modules/block-decl-2.C b/gcc/testsuite/g++.dg/modules/block-decl-2.C
index d491a18dfb1..104a98ab052 100644
--- a/gcc/testsuite/g++.dg/modules/block-decl-2.C
+++ b/gcc/testsuite/g++.dg/modules/block-decl-2.C
@@ -11,7 +11,7 @@  export extern "C++" auto foo() {
   struct X {
     // `foo` is not attached to a named module, and as such
     // `X::f` should be implicitly `inline` here
-    void f() {  // { dg-error "references internal linkage entity" }
+    void f() {  // { dg-error "exposes TU-local entity" }
       internal();
     }
   };
diff --git a/gcc/testsuite/g++.dg/modules/internal-1.C b/gcc/testsuite/g++.dg/modules/internal-1.C
index 9f7299a5fc7..3ed74c9cc42 100644
--- a/gcc/testsuite/g++.dg/modules/internal-1.C
+++ b/gcc/testsuite/g++.dg/modules/internal-1.C
@@ -3,13 +3,10 @@ 
 export module frob;
 // { dg-module-cmi !frob }
 
-namespace {
-// We shouldn't be complaining about members of internal linkage
-// entities
-class X  // { dg-bogus "internal linkage" "" { xfail *-*-* } }
-{ // { dg-bogus "internal linkage" "" { xfail *-*-* } }
-};
-
+namespace
+{
+  // We shouldn't be complaining about members of internal linkage entities
+  class X {};
 }
 
 static int frob () 
@@ -17,5 +14,5 @@  static int frob ()
   return 1;
 }
 
-export int f (int = frob ()); // { dg-error "references internal linkage" }
-int goof (X &); // { dg-error "references internal linkage" }
+export int f (int = frob ()); // { dg-error "exposes TU-local entity" }
+int goof (X &); // { dg-error "exposes TU-local entity" }
diff --git a/gcc/testsuite/g++.dg/modules/internal-3.C b/gcc/testsuite/g++.dg/modules/internal-3.C
new file mode 100644
index 00000000000..91aae32783f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/internal-3.C
@@ -0,0 +1,18 @@ 
+// { dg-additional-options "-fmodules-ts -Wno-global-module" }
+// { dg-module-cmi !M }
+// TU-local entities in the GMF can be exposed.
+
+module;
+
+static inline void foo() {}
+
+export module M;
+
+inline void bar() {  // { dg-error "exposes TU-local entity" }
+  foo();
+}
+
+// OK
+void qux() {
+  foo();
+}
diff --git a/gcc/testsuite/g++.dg/modules/internal-4_a.H b/gcc/testsuite/g++.dg/modules/internal-4_a.H
new file mode 100644
index 00000000000..04dfac8c936
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/internal-4_a.H
@@ -0,0 +1,4 @@ 
+// { dg-additional-options "-fmodule-header" }
+// { dg-module-cmi {} }
+
+static void header_f() {}
diff --git a/gcc/testsuite/g++.dg/modules/internal-4_b.C b/gcc/testsuite/g++.dg/modules/internal-4_b.C
new file mode 100644
index 00000000000..c5a3e80366e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/internal-4_b.C
@@ -0,0 +1,124 @@ 
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi !bad }
+// Test for determining various kinds of entities being marked TU-local
+
+export module bad;
+import "internal-4_a.H";
+
+
+// A type, function variable, or template with internal linkage
+namespace {
+  struct internal_t { int m; };
+  enum internal_e {};
+  void internal_f() {}
+  int internal_v;
+  template <typename T> void internal_x() {}
+}
+
+inline void expose_type() {  // { dg-error "exposes TU-local entity" }
+  internal_t x;
+}
+inline void expose_func() {  // { dg-error "exposes TU-local entity" }
+  internal_f();
+}
+inline void expose_var() {  // { dg-error "exposes TU-local entity" }
+  int* p = &internal_v;
+}
+inline void expose_tmpl() {  // { dg-error "exposes TU-local entity" }
+  internal_x<int>();
+}
+inline void expose_header_decl() {  // { dg-error "exposes TU-local entity" }
+  header_f();
+}
+
+// But we don't consider a weakref as being TU-local, despite being
+// marked static; this is to support uses of weakrefs in header files
+// (such as via the standard library).
+static void weakref() __attribute__((weakref("target")));
+inline void expose_weakref() {
+  weakref();
+}
+
+
+// Does not have a name with linkage and is declared, or introduced by
+// a lambda-expression, within the definition of a TU-local entity
+static auto get_local_ok() {
+  return 0;
+}
+static auto get_local_type() {
+  struct no_linkage {};
+  return no_linkage();
+}
+static auto get_local_lambda() {
+  return []{};
+}
+using T = decltype(get_local_ok());  // OK
+using U = decltype(get_local_type());  // { dg-error "exposes TU-local entity" }
+using V = decltype(get_local_lambda());  // { dg-error "exposes TU-local entity" }
+
+static auto internal_lambda = []{ internal_f(); };  // OK
+auto expose_lambda = internal_lambda;  // { dg-error "exposes TU-local entity" }
+
+int not_in_tu_local
+  = ([]{ internal_f(); }(),  // { dg-error "exposes TU-local entity" }
+     0);
+
+
+// A type with no name that is defined outside a class-specifier, function
+// body, or initializer
+
+struct {} no_name;  // { dg-error "exposes TU-local entity" }
+enum {} e;  // { dg-error "exposes TU-local entity" }
+using not_an_initializer = class {};  // { dg-error "exposes TU-local entity" }
+
+class in_class_specifier { struct {} x; };  // OK
+void in_function_body() { struct {} x; }  // OK
+auto in_initializer = []{};  // OK
+
+#if __cplusplus >= 202002L
+decltype([]{}) d_lambda;  // { dg-error "exposes TU-local entity" "" { target c++20 } }
+
+template <typename T>
+concept in_constraint_expression = requires {
+  // Strictly by the standard this is currently ill-formed
+  // (this is a constraint-expression not an initializer)
+  // but I don't think that is intended.
+  []{};  // OK?
+};
+#endif
+
+// (But consider unnamed types with names for linkage purposes as having names)
+typedef struct {} no_name_typedef_t;
+no_name_typedef_t linkage_name_struct;  // OK
+
+enum { enum_name } linkage_name_enum;  // OK
+
+
+// Specialisation of a TU-local template
+template <typename T> static void f(T) {}
+template <> void f(int) {}  // OK
+inline void f_use(int x) {  // { dg-error "exposes TU-local entity" }
+  f(x);
+}
+
+
+// Specialisation of a template with any TU-local argument
+template <typename T> void g(T) {}
+template <> void g(internal_t) { internal_f(); }  // OK
+template <> void g(internal_e) { internal_f(); }  // OK
+template <> void g(decltype(no_name)) { internal_f(); }  // OK
+template <> void g(decltype(get_local_lambda())) { internal_f(); }  // OK
+
+template <auto X> struct h {};
+template struct h<&internal_v>;
+template <> struct h<&internal_f> { internal_t x; };  // OK
+template <> struct h<&internal_t::m> { void foo() { internal_f(); } };  // OK
+
+
+// TODO: I can't come up with testcases for these that aren't already covered
+// by one of the above cases:
+//
+// - A type with no name introduced by a defining-type-specifier that is
+//   used to declare only TU-local entities
+// - A specialisation of a template whose (possibly instantiated) declaration
+//   is an exposure
diff --git a/gcc/testsuite/g++.dg/modules/linkage-2.C b/gcc/testsuite/g++.dg/modules/linkage-2.C
index 4b20411572c..97421bfad8e 100644
--- a/gcc/testsuite/g++.dg/modules/linkage-2.C
+++ b/gcc/testsuite/g++.dg/modules/linkage-2.C
@@ -25,6 +25,5 @@  export void use() {
 
 // Additionally, unnamed types have no linkage but are also TU-local, and thus
 // cannot be exposed in a module interface unit.  The non-TU-local entity 's'
-// here is an exposure of this type, so this should be an error; we don't yet
-// implement this checking however.
-struct {} s;  // { dg-error "TU-local" "" { xfail *-*-* } }
+// here is an exposure of this type.
+struct {} s;  // { dg-error "exposes TU-local entity" }