[clang] Revert "[clang] Give unnamed namespaces internal linkage" (PR #198738)
Hans Wennborg via cfe-commits
cfe-commits at lists.llvm.org
Wed May 20 02:49:12 PDT 2026
https://github.com/zmodem created https://github.com/llvm/llvm-project/pull/198738
This was causing assertion failures:
Assertion `D->getCachedLinkage() == LV.getLinkage()' failed.
see discussion on the original PR.
Reverts llvm/llvm-project#198215
>From 2e3671edd5c3401d295b7c341524cbfa00b92c4f Mon Sep 17 00:00:00 2001
From: Hans Wennborg <hans at hanshq.net>
Date: Wed, 20 May 2026 11:48:30 +0200
Subject: [PATCH] Revert "[clang] Give unnamed namespaces internal linkage
(#198215)"
This reverts commit 7ae1a3fad8f06625d4e55b846a090ce9206c3c82.
---
clang/lib/AST/Decl.cpp | 65 +++++++++-----------
clang/test/AST/ast-dump-linkage-internal.cpp | 12 +---
2 files changed, 33 insertions(+), 44 deletions(-)
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 79fd680737d00..535adcb2ae109 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -672,6 +672,23 @@ 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:
@@ -680,12 +697,6 @@ LinkageComputer::getLVForNamespaceScopeDecl(const NamedDecl *D,
// external.
LinkageInfo LV = getExternalLinkageFor(D);
- // Inherit linkage from the enclosing namespace, if there is any.
- 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)) {
LV.mergeVisibility(*Vis, true);
@@ -738,14 +749,6 @@ 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:
//
@@ -768,7 +771,8 @@ 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 && !IgnoreVarTypeLinkage) {
+ if (Context.getLangOpts().CPlusPlus && !isFirstInExternCContext(Var) &&
+ !IgnoreVarTypeLinkage) {
LinkageInfo TypeLV = getLVForType(*Var->getType(), computation);
if (!isExternallyVisible(TypeLV.getLinkage()))
return LinkageInfo::uniqueExternal();
@@ -813,27 +817,18 @@ 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.
- if (Context.getLangOpts().CPlusPlus) {
+ // this translation unit. However, we should use the C linkage
+ // rules instead for extern "C" declarations.
+ if (Context.getLangOpts().CPlusPlus && !isFirstInExternCContext(Function)) {
// 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())) {
- LV.mergeLinkage(LinkageInfo::uniqueExternal());
- return LV;
- }
+ if (!isExternallyVisible(TypeAsWritten->getLinkage()))
+ return LinkageInfo::uniqueExternal();
}
// Consider LV from the template and the template arguments.
@@ -881,13 +876,13 @@ 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.
- } else if (auto *ND = dyn_cast<NamespaceDecl>(D)) {
- if (ND->isAnonymousNamespace()) {
- LV.mergeLinkage(Linkage::Internal);
- }
+ //
+ // We handled names in anonymous namespaces above.
+ } else if (isa<NamespaceDecl>(D)) {
+ return LV;
- // 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
diff --git a/clang/test/AST/ast-dump-linkage-internal.cpp b/clang/test/AST/ast-dump-linkage-internal.cpp
index 10558526fbbdd..6b961301c4abb 100644
--- a/clang/test/AST/ast-dump-linkage-internal.cpp
+++ b/clang/test/AST/ast-dump-linkage-internal.cpp
@@ -1,7 +1,6 @@
// 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;
@@ -10,7 +9,8 @@ struct initializer_list {
} // namespace std
namespace {
-// CHECK: |-NamespaceDecl {{.*}} internal-linkage
+// CHECK: |-NamespaceDecl {{.*}} external-linkage
+// FIXME: Unnamed namespaces have internal linkage.
typedef int TypedefInt;
// CHECK: | |-TypedefDecl {{.*}} TypedefInt 'int'
@@ -57,7 +57,7 @@ enum Enum {};
// CHECK: | |-EnumDecl {{.*}} Enum internal-linkage
enum { Enumerator };
-// CHECK: | |-EnumDecl {{.*}}
+// CHECK: | |-EnumDecl {{.*}} internal-linkage
// CHECK: | | `-EnumConstantDecl {{.*}} referenced Enumerator '(anonymous namespace)::(unnamed enum at {{.*}})'
decltype(Enumerator) f();
@@ -77,9 +77,6 @@ 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
@@ -125,9 +122,6 @@ 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
More information about the cfe-commits
mailing list