https://github.com/Endilll created 
https://github.com/llvm/llvm-project/pull/198215

Recently in #194600 we exposed formal linkage in AST dump. That PR came with a 
bunch of FIXMEs. One of them is about the fact that we consider unnamed 
namespaces to have external linkage, while the Standard says it's internal 
linkage ([[basic.link]/4](https://eel.is/c++draft/basic.link#4.sentence-1)):

> An unnamed namespace or a namespace declared directly or indirectly within an 
> unnamed namespace has internal linkage.

Of course, declarations within unnamed namespaces still had internal linkage 
(nothing would work otherwise).

The intent of this patch is to give unnamed namespaces internal linkage and to 
do a bit of refactoring in `LinkageComputer::getLVForNamespaceScopeDecl` to use 
linkage of the enclosing namespace as the default linkage of declarations 
within it, now that all kinds of namespaces have the correct linkage. No 
changes to the behavior of programs are intended.

>From 9ac9e055dc0918610a2f0f2f51afb91610d130ee Mon Sep 17 00:00:00 2001
From: Vlad Serebrennikov <[email protected]>
Date: Mon, 18 May 2026 00:03:12 +0300
Subject: [PATCH 1/3] [clang] Give unnamed namespaces internal linkage

---
 clang/lib/AST/Decl.cpp                       | 59 ++++++++++----------
 clang/test/AST/ast-dump-linkage-internal.cpp | 12 +++-
 2 files changed, 40 insertions(+), 31 deletions(-)

diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 535adcb2ae109..b20a3b96e6f1b 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -672,30 +672,17 @@ LinkageComputer::getLVForNamespaceScopeDecl(const 
NamedDecl *D,
   }
   assert(!isa<FieldDecl>(D) && "Didn't expect a FieldDecl!");
 
-  // FIXME: This gives internal linkage to names that should have no linkage
-  // (those not covered by [basic.link]p6).
-  if (D->isInAnonymousNamespace()) {
-    const auto *Var = dyn_cast<VarDecl>(D);
-    const auto *Func = dyn_cast<FunctionDecl>(D);
-    // FIXME: The check for extern "C" here is not justified by the standard
-    // wording, but we retain it from the pre-DR1113 model to avoid breaking
-    // code.
-    //
-    // C++11 [basic.link]p4:
-    //   An unnamed namespace or a namespace declared directly or indirectly
-    //   within an unnamed namespace has internal linkage.
-    if ((!Var || !isFirstInExternCContext(Var)) &&
-        (!Func || !isFirstInExternCContext(Func)))
-      return LinkageInfo::internal();
-  }
-
   // Set up the defaults.
 
   // C99 6.2.2p5:
   //   If the declaration of an identifier for an object has file
   //   scope and no storage-class specifier, its linkage is
   //   external.
+
   LinkageInfo LV = getExternalLinkageFor(D);
+  if (const auto *ND = 
dyn_cast<NamespaceDecl>(D->getDeclContext()->getEnclosingNamespaceContext())) {
+    LV.setLinkage(ND->getLinkageInternal());
+  }
 
   if (!hasExplicitVisibilityAlready(computation)) {
     if (std::optional<Visibility> Vis = getExplicitVisibility(D, computation)) 
{
@@ -749,6 +736,14 @@ LinkageComputer::getLVForNamespaceScopeDecl(const 
NamedDecl *D,
 
   //     - a variable; or
   if (const auto *Var = dyn_cast<VarDecl>(D)) {
+    // FIXME: The check for extern "C" here is not justified by the standard
+    // wording, but we retain it from the pre-DR1113 model to avoid breaking
+    // code.
+    if (isFirstInExternCContext(Var)) {
+      LV.setLinkage(Linkage::External);
+      return LV;
+    }
+
     // GCC applies the following optimization to variables and static
     // data members, but not to functions:
     //
@@ -771,8 +766,7 @@ LinkageComputer::getLVForNamespaceScopeDecl(const NamedDecl 
*D,
     // Note that we don't want to make the variable non-external
     // because of this, but unique-external linkage suits us.
 
-    if (Context.getLangOpts().CPlusPlus && !isFirstInExternCContext(Var) &&
-        !IgnoreVarTypeLinkage) {
+    if (Context.getLangOpts().CPlusPlus && !IgnoreVarTypeLinkage) {
       LinkageInfo TypeLV = getLVForType(*Var->getType(), computation);
       if (!isExternallyVisible(TypeLV.getLinkage()))
         return LinkageInfo::uniqueExternal();
@@ -817,18 +811,27 @@ LinkageComputer::getLVForNamespaceScopeDecl(const 
NamedDecl *D,
     // merging storage classes and visibility attributes, so we don't have to
     // look at previous decls in here.
 
+    // FIXME: The check for extern "C" here is not justified by the standard
+    // wording, but we retain it from the pre-DR1113 model to avoid breaking
+    // code.
+    if (isFirstInExternCContext(Function)) {
+      LV.setLinkage(Linkage::External);
+      return LV;
+    }
+
     // In C++, then if the type of the function uses a type with
     // unique-external linkage, it's not legally usable from outside
-    // this translation unit.  However, we should use the C linkage
-    // rules instead for extern "C" declarations.
-    if (Context.getLangOpts().CPlusPlus && !isFirstInExternCContext(Function)) 
{
+    // this translation unit.
+    if (Context.getLangOpts().CPlusPlus) {
       // Only look at the type-as-written. Otherwise, deducing the return type
       // of a function could change its linkage.
       QualType TypeAsWritten = Function->getType();
       if (TypeSourceInfo *TSI = Function->getTypeSourceInfo())
         TypeAsWritten = TSI->getType();
-      if (!isExternallyVisible(TypeAsWritten->getLinkage()))
-        return LinkageInfo::uniqueExternal();
+      if (!isExternallyVisible(TypeAsWritten->getLinkage())) {
+        LV.mergeLinkage(LinkageInfo::uniqueExternal());
+        return LV;
+      }
     }
 
     // Consider LV from the template and the template arguments.
@@ -876,10 +879,10 @@ LinkageComputer::getLVForNamespaceScopeDecl(const 
NamedDecl *D,
   //     An unnamed namespace or a namespace declared directly or indirectly
   //     within an unnamed namespace has internal linkage. All other namespaces
   //     have external linkage.
-  //
-  // We handled names in anonymous namespaces above.
-  } else if (isa<NamespaceDecl>(D)) {
-    return LV;
+  } else if (auto *ND = dyn_cast<NamespaceDecl>(D)) {
+    if (ND->isAnonymousNamespace()) {
+      LV.mergeLinkage(Linkage::Internal);
+    }
 
   // By extension, we assign external linkage to Objective-C
   // interfaces.
diff --git a/clang/test/AST/ast-dump-linkage-internal.cpp 
b/clang/test/AST/ast-dump-linkage-internal.cpp
index 6b961301c4abb..10558526fbbdd 100644
--- a/clang/test/AST/ast-dump-linkage-internal.cpp
+++ b/clang/test/AST/ast-dump-linkage-internal.cpp
@@ -1,6 +1,7 @@
 // RUN: %clang_cc1 -ast-dump -std=c++2c %s | FileCheck --match-full-lines %s
 
 namespace std {
+// CHECK: |-NamespaceDecl {{.*}} std external-linkage
 template <typename T>
 struct initializer_list {
   const T* begin;
@@ -9,8 +10,7 @@ struct initializer_list {
 } // namespace std
 
 namespace {
-// CHECK: |-NamespaceDecl {{.*}} external-linkage
-// FIXME: Unnamed namespaces have internal linkage.
+// CHECK: |-NamespaceDecl {{.*}} internal-linkage
 
 typedef int TypedefInt;
 // CHECK: | |-TypedefDecl {{.*}} TypedefInt 'int'
@@ -57,7 +57,7 @@ enum Enum {};
 // CHECK: | |-EnumDecl {{.*}} Enum internal-linkage
 
 enum { Enumerator };
-// CHECK: | |-EnumDecl {{.*}} internal-linkage
+// CHECK: | |-EnumDecl {{.*}}
 // CHECK: | | `-EnumConstantDecl {{.*}} referenced Enumerator '(anonymous 
namespace)::(unnamed enum at {{.*}})'
 
 decltype(Enumerator) f();
@@ -77,6 +77,9 @@ int Int = 0;
 const int ConstInt = 0;
 // CHECK: | |-VarDecl {{.*}} ConstInt 'const int' cinit internal-linkage
 
+extern "C" int ExternCInt;
+// CHECK: | | `-VarDecl {{.*}} ExternCInt 'int' external-linkage
+
 template <typename T>
 T TemplatedVar = T{};
 // CHECK: | |-VarTemplateDecl {{.*}} TemplatedVar internal-linkage
@@ -122,6 +125,9 @@ namespace Known {
 void FuncDecl();
 // CHECK: | | |-FunctionDecl {{.*}} FuncDecl 'void ()' internal-linkage
 
+extern "C" void ExternCFuncDecl();
+// CHECK: | | | `-FunctionDecl {{.*}} ExternCFuncDecl 'void ()' 
external-linkage
+
 constexpr void ConstexprFuncDecl();
 // CHECK: | | |-FunctionDecl{{.*}} constexpr ConstexprFuncDecl 'void ()' 
implicit-inline internal-linkage
 

>From daaf6c1636d1ff058f2fcf268728e7e431eebd7d Mon Sep 17 00:00:00 2001
From: Vlad Serebrennikov <[email protected]>
Date: Mon, 18 May 2026 00:11:04 +0300
Subject: [PATCH 2/3] Run clang-format

---
 clang/lib/AST/Decl.cpp | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index b20a3b96e6f1b..f56f3b5e36292 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -680,7 +680,8 @@ LinkageComputer::getLVForNamespaceScopeDecl(const NamedDecl 
*D,
   //   external.
 
   LinkageInfo LV = getExternalLinkageFor(D);
-  if (const auto *ND = 
dyn_cast<NamespaceDecl>(D->getDeclContext()->getEnclosingNamespaceContext())) {
+  if (const auto *ND = dyn_cast<NamespaceDecl>(
+          D->getDeclContext()->getEnclosingNamespaceContext())) {
     LV.setLinkage(ND->getLinkageInternal());
   }
 
@@ -884,8 +885,8 @@ LinkageComputer::getLVForNamespaceScopeDecl(const NamedDecl 
*D,
       LV.mergeLinkage(Linkage::Internal);
     }
 
-  // By extension, we assign external linkage to Objective-C
-  // interfaces.
+    // By extension, we assign external linkage to Objective-C
+    // interfaces.
   } else if (isa<ObjCInterfaceDecl>(D)) {
     // fallout
 

>From 19f935add9bee1e8d2b48599cdc59732ee021f1b Mon Sep 17 00:00:00 2001
From: Vlad Serebrennikov <[email protected]>
Date: Mon, 18 May 2026 00:11:42 +0300
Subject: [PATCH 3/3] Remove a newline

---
 clang/lib/AST/Decl.cpp | 1 -
 1 file changed, 1 deletion(-)

diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index f56f3b5e36292..451122f339f89 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -678,7 +678,6 @@ LinkageComputer::getLVForNamespaceScopeDecl(const NamedDecl 
*D,
   //   If the declaration of an identifier for an object has file
   //   scope and no storage-class specifier, its linkage is
   //   external.
-
   LinkageInfo LV = getExternalLinkageFor(D);
   if (const auto *ND = dyn_cast<NamespaceDecl>(
           D->getDeclContext()->getEnclosingNamespaceContext())) {

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to