https://github.com/flash1729 updated 
https://github.com/llvm/llvm-project/pull/193567

>From 210026569ec49c47d38ee0e3e540b1e131c71a52 Mon Sep 17 00:00:00 2001
From: flash1729 <[email protected]>
Date: Wed, 15 Apr 2026 00:42:00 +0530
Subject: [PATCH 1/3] [Clang] Diagnose mixed internal/external linkage in C
 (reland)

C2y 6.7.1p7: an identifier shall not appear with both internal and
external linkage within a translation unit. Before C2y this was UB
(C11 6.2.2p7).

In C, a local shadow prevents a block-scope extern from inheriting
the file-scope static's internal linkage (C2y 6.2.2p4), so it
defaults to external linkage, creating the conflict.

This relands the original patch with fixes:
- Corrected C++ test: CWG 426 was superseded by P1787R6; Clang
  applies P1787R6 semantics so no linkage conflict exists in C++
- Added [dcl.meaning.general]/3.5 citation for C++ exclusion
- Restored C2y 6.2.2p4 citation

Fixes #54215
---
 clang/docs/ReleaseNotes.rst                   |  6 ++++
 .../clang/Basic/DiagnosticSemaKinds.td        |  3 ++
 clang/lib/Sema/SemaDecl.cpp                   | 21 +++++++++++
 clang/test/C/C2y/n3410.c                      | 10 +++---
 clang/test/Sema/linkage-internal-extern.c     | 36 +++++++++++++++++++
 clang/test/Sema/linkage-internal-extern.cpp   | 22 ++++++++++++
 clang/www/c_status.html                       |  2 +-
 7 files changed, 93 insertions(+), 7 deletions(-)
 create mode 100644 clang/test/Sema/linkage-internal-extern.c
 create mode 100644 clang/test/Sema/linkage-internal-extern.cpp

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 843a01f57c39f..060d68ef7f4d0 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -187,6 +187,12 @@ C2y Feature Support
   ``stdc_rotate_left_{uc,us,ui,ul,ull}`` and
   ``stdc_rotate_right_{uc,us,ui,ul,ull}``.
 
+- Clang now diagnoses the use of the same identifier with both internal and
+  external linkage within a translation unit, as made ill-formed by
+  `N3410 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3410.pdf>`_.
+  This is also diagnosed in older C language modes as the behavior was
+  undefined prior to C2y. (#GH54215)
+
 C23 Feature Support
 ^^^^^^^^^^^^^^^^^^^
 - Clang now allows C23 ``constexpr`` struct member access through the dot 
operator in constant expressions. (#GH178349)
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 9cd3a5b2fc049..2a942ae204dd8 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -6535,6 +6535,9 @@ def err_inline_declaration_block_scope : Error<
   "inline declaration of %0 not allowed in block scope">;
 def err_static_non_static : Error<
   "static declaration of %0 follows non-static declaration">;
+def err_internal_extern_mismatch : Error<
+  "variable %0 declared with both internal and external linkage "
+  "in the same translation unit%select{; behavior is undefined|}1">;
 def err_different_language_linkage : Error<
   "declaration of %0 has a different language linkage">;
 def ext_retained_language_linkage : Extension<
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 62cb9360d1322..1c29ec1a2bbd7 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -4822,6 +4822,27 @@ void Sema::MergeVarDecl(VarDecl *New, LookupResult 
&Previous) {
       return New->setInvalidDecl();
     }
   }
+
+  // C2y 6.7.1p7: an identifier shall not appear with both internal and
+  // external linkage within a translation unit. Before C2y this was UB
+  // (C11 6.2.2p7).
+  //
+  // In C, a local shadow prevents a block-scope extern from inheriting the
+  // file-scope static's internal linkage (C2y 6.2.2p4), so it defaults to
+  // external linkage, creating the conflict.
+  //
+  // In C++, block-scope extern declarations target the enclosing namespace
+  // scope ([dcl.meaning.general]/3.5), bypassing local shadows entirely, so
+  // the extern always inherits internal linkage. No conflict arises.
+  if (!getLangOpts().CPlusPlus && New->isLocalVarDecl() &&
+      New->hasExternalStorage() && Old->isFileVarDecl() && Old->hasLinkage() &&
+      Previous.isShadowed() && Old->getFormalLinkage() == Linkage::Internal) {
+    Diag(New->getLocation(), diag::err_internal_extern_mismatch)
+        << New->getDeclName() << getLangOpts().C2y;
+    Diag(OldLocation, PrevDiag);
+    return New->setInvalidDecl();
+  }
+
   // C99 6.2.2p4:
   //   For an identifier declared with the storage-class specifier
   //   extern in a scope in which a prior declaration of that
diff --git a/clang/test/C/C2y/n3410.c b/clang/test/C/C2y/n3410.c
index e1cb41f375b82..7ce3c2916d300 100644
--- a/clang/test/C/C2y/n3410.c
+++ b/clang/test/C/C2y/n3410.c
@@ -1,6 +1,6 @@
 // RUN: %clang_cc1 -verify -std=c2y -Wall -pedantic -Wno-unused %s
 
-/* WG14 N3410: No
+/* WG14 N3410: Clang 23
  * Slay Some Earthly Demons XI
  *
  * It is now ill-formed for the same identifier within a TU to have both
@@ -24,19 +24,17 @@ void func2() {
   extern int b; // Ok
 }
 
-static int c, d;
+static int c, d; // expected-note 2 {{previous definition is here}}
 void func3() {
   int c; // no linkage, different object from the one declared above.
   for (int d;;) {
     // This 'c' is the same as the one declared at file scope, but because of
     // the local scope 'c', the file scope 'c' is not visible.
-    // FIXME: This should be diagnosed under N3410.
-    extern int c;
+    extern int c; // expected-error {{declared with both internal and external 
linkage}}
     // This 'd' is the same as the one declared at file scope as well, but
     // because of the 'd' declared within the for loop, the file scope 'd' is
     // also not visible, same as with 'c'.
-    // FIXME: This should be diagnosed under N3410.
-    extern int d;
+    extern int d; // expected-error {{declared with both internal and external 
linkage}}
   }
   for (static int e;;) {
     extern int e; // Ok for the same reason as 'b' above.
diff --git a/clang/test/Sema/linkage-internal-extern.c 
b/clang/test/Sema/linkage-internal-extern.c
new file mode 100644
index 0000000000000..42591c5b21f0b
--- /dev/null
+++ b/clang/test/Sema/linkage-internal-extern.c
@@ -0,0 +1,36 @@
+// RUN: %clang_cc1 -verify -fsyntax-only %s
+
+// C11 6.2.2p7: same identifier with both internal and external linkage is UB.
+
+// Conflicting linkage (UB)
+
+static int x; // expected-note {{previous}}
+void test_basic_shadow(void) {
+    int x;
+    { extern int x; } // expected-error {{declared with both internal and 
external linkage}}
+}
+
+static int y; // expected-note {{previous}}
+void test_deep_nesting(void) {
+    int y;
+    { int y; { { extern int y; } } } // expected-error {{declared with both 
internal and external linkage}}
+}
+
+static int p; // expected-note {{previous}}
+void test_param_shadow(int p) {
+    { extern int p; } // expected-error {{declared with both internal and 
external linkage}}
+}
+
+// Valid cases
+
+static int a;
+void test_no_shadow(void) {
+    extern int a;
+}
+
+void test_no_file_scope(void) {
+    for (static int b = 0;;) {
+        extern int b;
+        break;
+    }
+}
diff --git a/clang/test/Sema/linkage-internal-extern.cpp 
b/clang/test/Sema/linkage-internal-extern.cpp
new file mode 100644
index 0000000000000..242f38978b4c5
--- /dev/null
+++ b/clang/test/Sema/linkage-internal-extern.cpp
@@ -0,0 +1,22 @@
+// RUN: %clang_cc1 -verify -fsyntax-only -std=c++17 %s
+
+// expected-no-diagnostics
+
+// In C++, block-scope extern declarations target the enclosing namespace
+// scope ([dcl.meaning.general]/3.5), so they find the file-scope static
+// despite local shadows and inherit internal linkage. No conflict arises.
+//
+// This differs from C, where a local shadow breaks linkage inheritance,
+// causing the conflict diagnosed by err_internal_extern_mismatch.
+
+// Example adapted from [basic.link]/6.
+static void f();
+static int i = 0;
+void g() {
+    extern void f();   // internal linkage
+    int i;             // no linkage
+    {
+        extern void f(); // internal linkage
+        extern int i;    // internal linkage
+    }
+}
diff --git a/clang/www/c_status.html b/clang/www/c_status.html
index 5270033471167..4b3695831de58 100644
--- a/clang/www/c_status.html
+++ b/clang/www/c_status.html
@@ -283,7 +283,7 @@ <h2 id="c2y">C2y implementation status</h2>
     <tr>
       <td>Slay Some Earthly Demons XI</td>
       <td><a 
href="https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3410.pdf";>N3410</a></td>
-      <td class="none" align="center">No</td>
+      <td class="unreleased" align="center">Clang 23</td>
        </tr>
     <tr>
       <td>Slay Some Earthly Demons XII</td>

>From 5b852b777b3030ae30a02ad68a14fd8d058b0fae Mon Sep 17 00:00:00 2001
From: flash1729 <[email protected]>
Date: Sat, 25 Apr 2026 23:16:13 +0530
Subject: [PATCH 2/3] fixup! [Clang] Diagnose mixed internal/external linkage
 in C (reland)

---
 clang/lib/Sema/SemaDecl.cpp                 |  4 ++--
 clang/test/Sema/linkage-internal-extern.c   | 12 +++++++++++-
 clang/test/Sema/linkage-internal-extern.cpp |  4 ++--
 3 files changed, 15 insertions(+), 5 deletions(-)

diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 1c29ec1a2bbd7..c80faf7c5b902 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -4835,8 +4835,8 @@ void Sema::MergeVarDecl(VarDecl *New, LookupResult 
&Previous) {
   // scope ([dcl.meaning.general]/3.5), bypassing local shadows entirely, so
   // the extern always inherits internal linkage. No conflict arises.
   if (!getLangOpts().CPlusPlus && New->isLocalVarDecl() &&
-      New->hasExternalStorage() && Old->isFileVarDecl() && Old->hasLinkage() &&
-      Previous.isShadowed() && Old->getFormalLinkage() == Linkage::Internal) {
+      New->hasExternalStorage() && Old->hasLinkage() && Previous.isShadowed() 
&&
+      Old->getFormalLinkage() == Linkage::Internal) {
     Diag(New->getLocation(), diag::err_internal_extern_mismatch)
         << New->getDeclName() << getLangOpts().C2y;
     Diag(OldLocation, PrevDiag);
diff --git a/clang/test/Sema/linkage-internal-extern.c 
b/clang/test/Sema/linkage-internal-extern.c
index 42591c5b21f0b..06f30e60261e2 100644
--- a/clang/test/Sema/linkage-internal-extern.c
+++ b/clang/test/Sema/linkage-internal-extern.c
@@ -21,6 +21,16 @@ void test_param_shadow(int p) {
     { extern int p; } // expected-error {{declared with both internal and 
external linkage}}
 }
 
+static int z;
+void test_chained_shadow(void) {
+    extern int z;      // expected-note {{previous}} #2: internal linkage
+    {
+        int z;         // shadow: no linkage
+        { extern int z; } // expected-error {{declared with both internal and 
external linkage}}
+    }
+}
+
+
 // Valid cases
 
 static int a;
@@ -29,7 +39,7 @@ void test_no_shadow(void) {
 }
 
 void test_no_file_scope(void) {
-    for (static int b = 0;;) {
+    for (int b = 0;;) {
         extern int b;
         break;
     }
diff --git a/clang/test/Sema/linkage-internal-extern.cpp 
b/clang/test/Sema/linkage-internal-extern.cpp
index 242f38978b4c5..f20822a5635ae 100644
--- a/clang/test/Sema/linkage-internal-extern.cpp
+++ b/clang/test/Sema/linkage-internal-extern.cpp
@@ -3,8 +3,8 @@
 // expected-no-diagnostics
 
 // In C++, block-scope extern declarations target the enclosing namespace
-// scope ([dcl.meaning.general]/3.5), so they find the file-scope static
-// despite local shadows and inherit internal linkage. No conflict arises.
+// scope ([dcl.meaning.general]/3.5), so they match against the namespace-scope
+// static despite local shadows and inherit internal linkage. No conflict 
arises.
 //
 // This differs from C, where a local shadow breaks linkage inheritance,
 // causing the conflict diagnosed by err_internal_extern_mismatch.

>From e4ae3215da4ad5b23a9f83528ee6710b577268c6 Mon Sep 17 00:00:00 2001
From: flash1729 <[email protected]>
Date: Tue, 26 May 2026 16:58:29 +0530
Subject: [PATCH 3/3] fixup! [Clang] Diagnose mixed internal/external linkage
 in C (reland)

---
 clang/lib/Sema/SemaDecl.cpp                 |  2 +-
 clang/test/Sema/linkage-internal-extern.cpp | 33 ++++++++++++++++-----
 2 files changed, 27 insertions(+), 8 deletions(-)

diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index c80faf7c5b902..87f410927829e 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -4835,7 +4835,7 @@ void Sema::MergeVarDecl(VarDecl *New, LookupResult 
&Previous) {
   // scope ([dcl.meaning.general]/3.5), bypassing local shadows entirely, so
   // the extern always inherits internal linkage. No conflict arises.
   if (!getLangOpts().CPlusPlus && New->isLocalVarDecl() &&
-      New->hasExternalStorage() && Old->hasLinkage() && Previous.isShadowed() 
&&
+      New->hasExternalStorage() && Previous.isShadowed() &&
       Old->getFormalLinkage() == Linkage::Internal) {
     Diag(New->getLocation(), diag::err_internal_extern_mismatch)
         << New->getDeclName() << getLangOpts().C2y;
diff --git a/clang/test/Sema/linkage-internal-extern.cpp 
b/clang/test/Sema/linkage-internal-extern.cpp
index f20822a5635ae..27b9566620289 100644
--- a/clang/test/Sema/linkage-internal-extern.cpp
+++ b/clang/test/Sema/linkage-internal-extern.cpp
@@ -1,6 +1,4 @@
-// RUN: %clang_cc1 -verify -fsyntax-only -std=c++17 %s
-
-// expected-no-diagnostics
+// RUN: %clang_cc1 -ast-dump -std=c++17 %s | FileCheck %s
 
 // In C++, block-scope extern declarations target the enclosing namespace
 // scope ([dcl.meaning.general]/3.5), so they match against the namespace-scope
@@ -11,12 +9,33 @@
 
 // Example adapted from [basic.link]/6.
 static void f();
+// CHECK: FunctionDecl {{.*}} f 'void ()' static internal-linkage
 static int i = 0;
+// CHECK: VarDecl {{.*}} i 'int' static cinit internal-linkage
 void g() {
-    extern void f();   // internal linkage
-    int i;             // no linkage
+// CHECK: FunctionDecl {{.*}} g 'void ()' external-linkage
+    extern void f();
+    // CHECK: FunctionDecl {{.*}} prev {{.*}} f 'void ()' extern 
internal-linkage
+    int i;
+    // CHECK: VarDecl {{.*}} i 'int'{{$}}
+    {
+        extern void f();
+        // CHECK: FunctionDecl {{.*}} prev {{.*}} f 'void ()' extern 
internal-linkage
+        extern int i;
+        // CHECK: VarDecl {{.*}} prev {{.*}} i 'int' extern internal-linkage
+    }
+}
+
+// Block-scope function declarations behave identically without extern
+// (C11 6.2.2p5, C++ [dcl.stc]p5).
+static void h();
+// CHECK: FunctionDecl {{.*}} h 'void ()' static internal-linkage
+void g2() {
+// CHECK: FunctionDecl {{.*}} g2 'void ()' external-linkage
+    int h;
+    // CHECK: VarDecl {{.*}} h 'int'{{$}}
     {
-        extern void f(); // internal linkage
-        extern int i;    // internal linkage
+        void h();
+        // CHECK: FunctionDecl {{.*}} prev {{.*}} h 'void ()' internal-linkage
     }
 }

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

Reply via email to