On 3/7/25 6:00 AM, Nathaniel Shead wrote:
On Fri, Mar 07, 2025 at 09:48:27PM +1100, Nathaniel Shead wrote:
On Thu, Mar 06, 2025 at 11:20:59AM -0500, Jason Merrill wrote:
On 2/9/25 6:38 AM, Nathaniel Shead wrote:
On Sun, Feb 09, 2025 at 01:16:00AM +1100, Nathaniel Shead wrote:
Tested on x86_64-pc-linux-gnu, OK for trunk if full bootstrap + regtest
passes?

-- >8 --

There are two issues with no-linkage decls (e.g. explicit type aliases)
in unnamed namespaces that this patch fixes.

Firstly, we don't currently handle exporting no-linkage decls in unnamed
namespaces.  This should be ill-formed in [module.export], since having
an exported declaration within a namespace-definition makes the
namespace definition exported (p2), but an unnamed namespace has
internal linkage thus violating p3.

Secondly, by the standard it appears to be possible to emit unnamed
namespaces from named modules in certain scenarios.  This patch makes
the adjustments needed to ensure we don't error in this case.


One thing to note with this is that it means that the following sample:

    export module M;
    namespace {
      struct Internal {};
      using Alias = Internal;
    }

will still error:

test.cpp:4:9: error: ‘using {anonymous}::Alias = struct {anonymous}::Internal’ 
exposes TU-local entity ‘struct {anonymous}::Internal’
      4 |   using Alias = Internal;
        |         ^~~~~
test.cpp:3:10: note: ‘struct {anonymous}::Internal’ declared with internal 
linkage
      3 |   struct Internal {};
        |          ^~~~~~~~

https://eel.is/c++draft/basic.link#17 is the relevant paragraph here,
but I'm not 100% sure what it says about this example; I suppose that
given Alias isn't really an entity maybe this should be OK?

Right; a non-template alias isn't an entity, and p17 only makes exposure
ill-formed if the declaration declares an entity.

But either way we definitely don't want to emit this alias.

Maybe the correct approach here is to mark an explicit type alias
TU-local iff the type it refers to is itself TU-local?  So something
like this on top of this patch:

Hmm, since it isn't an entity, it also isn't a TU-local entity.  Maybe in
finalize_dependencies, ignore is_exposure if the dep is not an entity?

Jason


Unfortunately this is not sufficient, as none of the following streaming
logic is prepared to handle TU-local dependencies.  I think marking
these aliases as TU-local is the most straightforward way to achieve the
required semantics of them not being streamed, since we don't really
have a general way to talk about deps referring to non-entities and how
they should be handled.

I've done this in the below patch, OK for trunk?

Whoops, attached the wrong version of the patch...

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

OK.

-- >8 --

There are two issues with no-linkage decls (e.g. explicit type aliases)
in unnamed namespaces that this patch fixes.

Firstly, we don't currently handle exporting no-linkage decls in unnamed
namespaces.  This should be ill-formed in [module.export], since having
an exported declaration within a namespace-definition makes the
namespace definition exported (p2), but an unnamed namespace has
internal linkage thus violating p3.

Secondly, by the standard it appears to be possible to emit unnamed
namespaces from named modules in certain scenarios, for instance when
they contain a type alias (which is not itself an entity).  This patch
makes the adjustments needed to ensure we don't error in this scenario.

        PR c++/118799

gcc/cp/ChangeLog:

        * module.cc (depset::hash::is_tu_local_entity): Only types,
        functions, variables, and template (specialisations) can be
        TU-local.  Explicit type aliases are TU-local iff the type they
        refer to are.
        (module_state::write_namespaces): Allow unnamed namespaces in
        named modules.
        (check_module_decl_linkage): Error for all exported declarations
        in an unnamed namespace.

gcc/testsuite/ChangeLog:

        * g++.dg/modules/export-6.C: Adjust error message, add check for
        no-linkage decls in namespace.
        * g++.dg/modules/internal-4_b.C: Allow exposing a namespace with
        internal linkage.  Type aliases are not entities and so never
        exposures.
        * g++.dg/modules/using-30_a.C: New test.
        * g++.dg/modules/using-30_b.C: New test.
        * g++.dg/modules/using-30_c.C: New test.

Signed-off-by: Nathaniel Shead <nathanielosh...@gmail.com>
---
  gcc/cp/module.cc                            | 58 ++++++++++++++++-----
  gcc/testsuite/g++.dg/modules/export-6.C     | 33 +++++++-----
  gcc/testsuite/g++.dg/modules/internal-4_b.C | 19 ++++---
  gcc/testsuite/g++.dg/modules/using-30_a.C   | 13 +++++
  gcc/testsuite/g++.dg/modules/using-30_b.C   | 10 ++++
  gcc/testsuite/g++.dg/modules/using-30_c.C   | 17 ++++++
  6 files changed, 116 insertions(+), 34 deletions(-)
  create mode 100644 gcc/testsuite/g++.dg/modules/using-30_a.C
  create mode 100644 gcc/testsuite/g++.dg/modules/using-30_b.C
  create mode 100644 gcc/testsuite/g++.dg/modules/using-30_c.C

diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index 8b0f42951c2..0c50050cc71 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -13403,16 +13403,35 @@ bool
  depset::hash::is_tu_local_entity (tree decl, bool explain/*=false*/)
  {
    gcc_checking_assert (DECL_P (decl));
+  location_t loc = DECL_SOURCE_LOCATION (decl);
+  tree type = TREE_TYPE (decl);
+
+  /* Only types, functions, variables, and template (specialisations)
+     can be TU-local.  */
+  if (TREE_CODE (decl) != TYPE_DECL
+      && TREE_CODE (decl) != FUNCTION_DECL
+      && TREE_CODE (decl) != VAR_DECL
+      && TREE_CODE (decl) != TEMPLATE_DECL)
+    return false;
- /* An explicit type alias is not an entity, and so is never TU-local.
-     Neither are the built-in declarations of 'int' and such.  */
+  /* An explicit type alias is not an entity; we don't want to stream
+     such aliases if they refer to TU-local entities, so propagate this
+     from the original type. The built-in declarations of 'int' and such
+     are never TU-local.  */
    if (TREE_CODE (decl) == TYPE_DECL
        && !DECL_SELF_REFERENCE_P (decl)
        && !DECL_IMPLICIT_TYPEDEF_P (decl))
-    return false;
-
-  location_t loc = DECL_SOURCE_LOCATION (decl);
-  tree type = TREE_TYPE (decl);
+    {
+      tree orig = DECL_ORIGINAL_TYPE (decl);
+      if (orig && TYPE_NAME (orig))
+       {
+         if (explain)
+           inform (loc, "%qD is an alias of TU-local type %qT", decl, orig);
+         return is_tu_local_entity (TYPE_NAME (orig), explain);
+       }
+      else
+       return false;
+    }
/* Check specializations first for slightly better explanations. */
    int use_tpl = -1;
@@ -16510,8 +16529,9 @@ module_state::write_namespaces (elf_out *to, vec<depset 
*> spaces,
        depset *b = spaces[ix];
        tree ns = b->get_entity ();
+ /* This could be an anonymous namespace even for a named module,
+        since we can still emit no-linkage decls.  */
        gcc_checking_assert (TREE_CODE (ns) == NAMESPACE_DECL);
-      gcc_checking_assert (TREE_PUBLIC (ns) || header_module_p ());
unsigned flags = 0;
        if (TREE_PUBLIC (ns))
@@ -20481,13 +20501,25 @@ check_module_decl_linkage (tree decl)
    /* An internal-linkage declaration cannot be generally be exported.
       But it's OK to export any declaration from a header unit, including
       internal linkage declarations.  */
-  if (!header_module_p ()
-      && DECL_MODULE_EXPORT_P (decl)
-      && decl_linkage (decl) == lk_internal)
+  if (!header_module_p () && DECL_MODULE_EXPORT_P (decl))
      {
-      error_at (DECL_SOURCE_LOCATION (decl),
-               "exporting declaration %qD with internal linkage", decl);
-      DECL_MODULE_EXPORT_P (decl) = false;
+      /* Let's additionally treat any exported declaration within an
+        internal namespace as exporting a declaration with internal
+        linkage, as this would also implicitly export the internal
+        linkage namespace.  */
+      if (decl_internal_context_p (decl))
+       {
+         error_at (DECL_SOURCE_LOCATION (decl),
+                   "exporting declaration %qD declared in unnamed namespace",
+                   decl);
+         DECL_MODULE_EXPORT_P (decl) = false;
+       }
+      else if (decl_linkage (decl) == lk_internal)
+       {
+         error_at (DECL_SOURCE_LOCATION (decl),
+                   "exporting declaration %qD with internal linkage", decl);
+         DECL_MODULE_EXPORT_P (decl) = false;
+       }
      }
  }
diff --git a/gcc/testsuite/g++.dg/modules/export-6.C b/gcc/testsuite/g++.dg/modules/export-6.C
index 460cdf08ea9..af54e5fbe87 100644
--- a/gcc/testsuite/g++.dg/modules/export-6.C
+++ b/gcc/testsuite/g++.dg/modules/export-6.C
@@ -16,27 +16,32 @@ export static auto [d] = S{};  // { dg-error "internal linkage" 
"" { target c++2
  #endif
namespace {
-  export int y = 456;  // { dg-error "internal linkage" }
-  export void h();  // { dg-error "internal linkage" }
-  export void i() {}  // { dg-error "internal linkage" }
-  export template <typename T> void v(); // { dg-error "internal linkage" }
-  export template <typename T> void w() {} // { dg-error "internal linkage" }
-  export auto [e] = S{};  // { dg-error "internal linkage" }
+  export int y = 456;  // { dg-error "exporting" }
+  export void h();  // { dg-error "exporting" }
+  export void i() {}  // { dg-error "exporting" }
+  export template <typename T> void v(); // { dg-error "exporting" }
+  export template <typename T> void w() {} // { dg-error "exporting" }
+  export auto [e] = S{};  // { dg-error "exporting" }
- export namespace ns {} // { dg-error "internal linkage" }
-  export namespace alias = global;  // { dg-error "internal linkage" }
+  export namespace ns {}  // { dg-error "exporting" }
+  export namespace alias = global;  // { dg-error "exporting" }
- export struct A {}; // { dg-error "internal linkage" }
-  export template <typename T> struct B {};  // { dg-error "internal linkage" }
+  export struct A {};  // { dg-error "exporting" }
+  export template <typename T> struct B {};  // { dg-error "exporting" }
- export enum E {}; // { dg-error "internal linkage" }
-  export enum class F {};  // { dg-error "internal linkage" }
+  export enum E {};  // { dg-error "exporting" }
+  export enum class F {};  // { dg-error "exporting" }
- export template <typename T> using U = int; // { dg-error "internal linkage" }
+  export template <typename T> using U = int;  // { dg-error "exporting" }
#if __cplusplus >= 202002L
-  export template <typename T> concept C = true;  // { dg-error "internal linkage" 
"" { target c++20 } }
+  export template <typename T> concept C = true;  // { dg-error "exporting" "" 
{ target c++20 } }
  #endif
+
+  // Also complain about exporting no-linkage decls in an unnamed namespace
+  export typedef int T;  // { dg-error "exporting" }
+  export typedef struct {} *PC;  // { dg-error "exporting" }
+  export using V = int;  // { dg-error "exporting" }
  }
export namespace {} // { dg-error "exporting unnamed namespace" }
diff --git a/gcc/testsuite/g++.dg/modules/internal-4_b.C 
b/gcc/testsuite/g++.dg/modules/internal-4_b.C
index 81fc65a69bd..a6ccb43b446 100644
--- a/gcc/testsuite/g++.dg/modules/internal-4_b.C
+++ b/gcc/testsuite/g++.dg/modules/internal-4_b.C
@@ -32,8 +32,8 @@ inline void expose_header_decl() {  // { dg-error "exposes 
TU-local entity" }
    header_f();
  }
-// We additionally consider a namespace with internal linkage as TU-local
-namespace expose_ns = internal_ns;  // { dg-error "exposes TU-local entity" }
+// A namespace with internal linkage is not inherently an exposure.
+namespace expose_ns = internal_ns;  // { dg-bogus "exposes TU-local entity" }
// 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
@@ -56,9 +56,12 @@ static auto get_local_type() {
  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" }
+using T = decltype(get_local_ok());
+T* expose_t;   // OK
+using U = decltype(get_local_type());
+U* expose_u;  // { dg-error "exposes TU-local entity" }
+using V = decltype(get_local_lambda());
+V* expose_v;  // { dg-error "exposes TU-local entity" }
static auto internal_lambda = []{ internal_f(); }; // OK
  auto expose_lambda = internal_lambda;  // { dg-error "exposes TU-local 
entity" }
@@ -73,7 +76,8 @@ int not_in_tu_local
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" }
+using not_an_initializer = class {};
+not_an_initializer expose_nai;  // { dg-error "exposes TU-local entity" }
class in_class_specifier { struct {} x; }; // OK
  void in_function_body() { struct {} x; }  // OK
@@ -81,7 +85,8 @@ auto in_initializer = []{};  // OK
#if __cplusplus >= 202002L
  decltype([]{}) d_lambda;  // { dg-error "exposes TU-local entity" "" { target 
c++20 } }
-using alias_lambda = decltype([]{});  // { dg-error "exposes TU-local entity" 
"" { target c++20 } }
+using alias_lambda = decltype([]{});
+alias_lambda expose_lam;  // { dg-error "exposes TU-local entity" "" { target 
c++20 } }
template <typename T>
  concept in_constraint_expression = requires {
diff --git a/gcc/testsuite/g++.dg/modules/using-30_a.C 
b/gcc/testsuite/g++.dg/modules/using-30_a.C
new file mode 100644
index 00000000000..def719ce3e5
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/using-30_a.C
@@ -0,0 +1,13 @@
+// { dg-additional-options "-fmodules" }
+// { dg-module-cmi M }
+
+export module M;
+
+namespace {
+  using A = int;
+  typedef int B;
+
+  struct Internal {};
+  using C = Internal;
+  typedef Internal D;
+}
diff --git a/gcc/testsuite/g++.dg/modules/using-30_b.C 
b/gcc/testsuite/g++.dg/modules/using-30_b.C
new file mode 100644
index 00000000000..aeee19f4887
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/using-30_b.C
@@ -0,0 +1,10 @@
+// { dg-additional-options "-fmodules" }
+
+module M;
+
+A x;
+B y;
+
+// Aliases that refer to TU-local entities should not be visible.
+C c;  // { dg-error "does not name a type" }
+D d;  // { dg-error "does not name a type" }
diff --git a/gcc/testsuite/g++.dg/modules/using-30_c.C 
b/gcc/testsuite/g++.dg/modules/using-30_c.C
new file mode 100644
index 00000000000..5ba59817da4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/using-30_c.C
@@ -0,0 +1,17 @@
+// { dg-additional-options "-fmodules" }
+// The different declarations in the anonymous namespace shouldn't clash with
+// those in M.
+
+namespace {
+  using A = double;
+  typedef double B;
+  using C = double;
+  typedef double D;
+}
+import M;
+int main() {
+  A a = 1.0;
+  B b = 2.0;
+  C c = 3.0;
+  D d = 4.0;
+}

Reply via email to