https://github.com/cerisier updated 
https://github.com/llvm/llvm-project/pull/233862

>From 1d9f96d623c3bf532458b71d05079ce4d4504156 Mon Sep 17 00:00:00 2001
From: Corentin Kerisit <[email protected]>
Date: Tue, 20 Oct 2026 16:13:54 +0200
Subject: [PATCH 1/4] [Clang] Add failing tests for inline namespace
 redeclarations

Add intentionally failing regression tests for namespace extensions and 
qualified tag definitions found through inline namespaces (LLVM #95154).

Check semantic ownership, name lookup, mangling, and PCH serialization. All 
three tests fail on unmodified Clang; the following implementation commit fixes 
them.
---
 ...t-dump-inline-namespace-redeclarations.cpp | 39 +++++++++++++
 ...mangle-inline-namespace-redeclarations.cpp | 58 +++++++++++++++++++
 .../inline-namespace-redeclarations.cpp       | 32 ++++++++++
 3 files changed, 129 insertions(+)
 create mode 100644 clang/test/AST/ast-dump-inline-namespace-redeclarations.cpp
 create mode 100644 
clang/test/CodeGenCXX/mangle-inline-namespace-redeclarations.cpp
 create mode 100644 clang/test/SemaCXX/inline-namespace-redeclarations.cpp

diff --git a/clang/test/AST/ast-dump-inline-namespace-redeclarations.cpp 
b/clang/test/AST/ast-dump-inline-namespace-redeclarations.cpp
new file mode 100644
index 00000000000000..197a5c85f7a7fb
--- /dev/null
+++ b/clang/test/AST/ast-dump-inline-namespace-redeclarations.cpp
@@ -0,0 +1,39 @@
+// RUN: %clang_cc1 -std=c++11 -ast-dump %s | FileCheck %s
+// RUN: %clang_cc1 -std=c++11 -emit-pch -o %t %s
+// RUN: %clang_cc1 -std=c++11 -include-pch %t -ast-dump-all -x c++ /dev/null | 
FileCheck %s
+
+namespace reopen {
+// CHECK: NamespaceDecl [[V1:0x[^ ]+]] {{.*}} v1 inline
+inline namespace v1 {
+// CHECK: NamespaceDecl [[NS:0x[^ ]+]] {{.*}} ns
+namespace ns {
+struct Y;
+}
+}
+// CHECK: NamespaceDecl {{.*}} parent [[V1]] prev [[NS]] {{.*}} ns
+namespace ns {
+struct Y {};
+}
+}
+
+namespace qualified {
+// CHECK: NamespaceDecl [[QV1:0x[^ ]+]] {{.*}} v1 inline
+inline namespace v1 {
+// CHECK: CXXRecordDecl [[X:0x[^ ]+]] {{.*}} struct X
+struct X;
+}
+}
+namespace alias = qualified;
+// CHECK: CXXRecordDecl {{.*}} parent [[QV1]] prev [[X]] {{.*}} struct X 
definition
+struct alias::X {};
+
+// Ordinary namespace redeclarations keep the current enclosing declaration
+// as both their lexical and semantic context.
+namespace ordinary {
+// CHECK: NamespaceDecl [[NESTED:0x[^ ]+]] {{.*}} nested
+namespace nested {}
+}
+namespace ordinary {
+// CHECK: NamespaceDecl {{0x[^ ]+}} prev [[NESTED]] {{.*}} nested
+namespace nested {}
+}
diff --git a/clang/test/CodeGenCXX/mangle-inline-namespace-redeclarations.cpp 
b/clang/test/CodeGenCXX/mangle-inline-namespace-redeclarations.cpp
new file mode 100644
index 00000000000000..fe1f5dbe72f91a
--- /dev/null
+++ b/clang/test/CodeGenCXX/mangle-inline-namespace-redeclarations.cpp
@@ -0,0 +1,58 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++11 -emit-llvm -o - 
%s | FileCheck %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -emit-llvm -o - 
%s | FileCheck %s
+
+// GH95154: reopening a namespace through an inline namespace must not change
+// the namespace's semantic parent, including for newly declared members.
+namespace reopen {
+inline namespace v1 {
+namespace ns {
+struct Y;
+}
+}
+
+// CHECK-DAG: define {{.*}} @_ZN6reopen6beforeEPNS_2v12ns1YE(
+void before(v1::ns::Y *) {}
+
+namespace ns {
+struct X {};
+struct Y {};
+
+// CHECK-DAG: define {{.*}} @_ZN6reopen2v12ns1fEv(
+void f() {}
+// CHECK-DAG: @_ZN6reopen2v12ns5valueE =
+int value;
+}
+
+// CHECK-DAG: define {{.*}} @_ZN6reopen5afterEPNS_2v12ns1YE(
+void after(v1::ns::Y *) {}
+// CHECK-DAG: define {{.*}} @_ZN6reopen4takeEPNS_2v12ns1XE(
+void take(v1::ns::X *) {}
+}
+
+// A qualified class definition can find its declaration through an inline
+// namespace, as in libstdc++'s filesystem::_Dir definition.
+namespace qualified {
+inline namespace v1 {
+struct X;
+}
+}
+namespace alias = qualified;
+
+// CHECK-DAG: define {{.*}} @_Z6beforePN9qualified2v11XE(
+void before(alias::X *) {}
+
+struct alias::X {
+  void member() {}
+};
+
+// CHECK-DAG: define {{.*}} @_Z5afterPN9qualified2v11XE(
+void after(alias::X *p) { p->member(); }
+// CHECK-DAG: define {{.*}} @_ZN9qualified2v11X6memberEv(
+
+// An ordinary namespace must keep its existing mangling.
+namespace ordinary {
+struct X;
+}
+struct ordinary::X {};
+// CHECK-DAG: define {{.*}} @_Z4takePN8ordinary1XE(
+void take(ordinary::X *) {}
diff --git a/clang/test/SemaCXX/inline-namespace-redeclarations.cpp 
b/clang/test/SemaCXX/inline-namespace-redeclarations.cpp
new file mode 100644
index 00000000000000..6b6063f62da40d
--- /dev/null
+++ b/clang/test/SemaCXX/inline-namespace-redeclarations.cpp
@@ -0,0 +1,32 @@
+// RUN: %clang_cc1 -std=c++11 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s
+// expected-no-diagnostics
+
+// Lookup must start in the semantic parent, rather than making the names in
+// the inline namespace ambiguous with those in its enclosing namespace.
+namespace reopen {
+constexpr int value = 0;
+inline namespace v1 {
+constexpr int value = 1;
+namespace ns {}
+}
+namespace ns {
+static_assert(value == 1, "lookup in the inline namespace");
+}
+}
+
+namespace qualified {
+constexpr int value = 0;
+inline namespace v1 {
+constexpr int value = 1;
+struct X;
+enum class E;
+}
+}
+namespace alias = qualified;
+struct alias::X {
+  static_assert(value == 1, "lookup in the inline namespace");
+};
+enum class alias::E { test = value };
+static_assert(static_cast<int>(alias::E::test) == 1,
+              "enum definitions retain their namespace too");

>From 7f74c2d74c64b4cdd8126d60510e88b42c010b70 Mon Sep 17 00:00:00 2001
From: Corentin Kerisit <[email protected]>
Date: Tue, 20 Oct 2026 16:14:03 +0200
Subject: [PATCH 2/4] [Clang] Preserve semantic parents across inline namespace
 redeclarations

Namespace extensions and qualified tag redeclarations found through inline 
namespaces must retain their original semantic parent. Preserve that ownership 
while keeping the lexical declaration context where the declaration was written.

This fixes name lookup and mangling that omitted the inline namespace and could 
merge distinct ABI symbols. The preceding regression tests fail before this 
change and pass afterward.

Fixes #95154.
---
 clang/lib/Sema/SemaDecl.cpp    |  6 ++++++
 clang/lib/Sema/SemaDeclCXX.cpp | 10 +++++++++-
 2 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 886b2a0b4c6d75..3aa5c3d073c241 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -18945,6 +18945,12 @@ Sema::ActOnTag(Scope *S, unsigned TagSpec, TagUseKind 
TUK, SourceLocation KWLoc,
             // tag. We're going to create a new Decl for it.
           }
 
+          // Qualified lookup can find a tag in an inline namespace. Its
+          // redeclaration belongs to that namespace, which need not be the
+          // context named by the qualifier.
+          if (SS.isNotEmpty())
+            SearchDC = PrevTagDecl->getDeclContext();
+
           // Okay, we're going to make a redeclaration.  If this is some kind
           // of reference, make sure we build the redeclaration in the same DC
           // as the original, and ignore the current access specifier.
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index cd9771526bee37..d7a2011b0c78cd 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -12097,8 +12097,16 @@ Decl *Sema::ActOnStartNamespaceDef(Scope *NamespcScope,
                                       &IsInline, PrevNS);
   }
 
+  // Namespace lookup can find a namespace in an inline namespace. An extension
+  // of that namespace retains its semantic parent, even when it is written in
+  // an enclosing namespace.
+  DeclContext *SemanticContext = CurContext;
+  if (PrevNS && !CurContext->getRedeclContext()->Equals(
+                    PrevNS->getDeclContext()->getRedeclContext()))
+    SemanticContext = PrevNS->getDeclContext();
   NamespaceDecl *Namespc = NamespaceDecl::Create(
-      Context, CurContext, IsInline, StartLoc, Loc, II, PrevNS, IsNested);
+      Context, SemanticContext, IsInline, StartLoc, Loc, II, PrevNS, IsNested);
+  Namespc->setLexicalDeclContext(CurContext);
   if (IsInvalid)
     Namespc->setInvalidDecl();
 

>From a91304a38cac0f6773e932fbb66bc29a7dc9628c Mon Sep 17 00:00:00 2001
From: Corentin Kerisit <[email protected]>
Date: Thu, 22 Oct 2026 09:52:36 +0200
Subject: [PATCH 3/4] [Clang] Clarify namespace boundaries in inline namespace
 tests

---
 .../ast-dump-inline-namespace-redeclarations.cpp | 16 ++++++++--------
 .../mangle-inline-namespace-redeclarations.cpp   | 14 +++++++-------
 .../SemaCXX/inline-namespace-redeclarations.cpp  | 10 +++++-----
 3 files changed, 20 insertions(+), 20 deletions(-)

diff --git a/clang/test/AST/ast-dump-inline-namespace-redeclarations.cpp 
b/clang/test/AST/ast-dump-inline-namespace-redeclarations.cpp
index 197a5c85f7a7fb..3e60028418fa97 100644
--- a/clang/test/AST/ast-dump-inline-namespace-redeclarations.cpp
+++ b/clang/test/AST/ast-dump-inline-namespace-redeclarations.cpp
@@ -8,21 +8,21 @@ inline namespace v1 {
 // CHECK: NamespaceDecl [[NS:0x[^ ]+]] {{.*}} ns
 namespace ns {
 struct Y;
-}
-}
+} // namespace ns
+} // namespace v1
 // CHECK: NamespaceDecl {{.*}} parent [[V1]] prev [[NS]] {{.*}} ns
 namespace ns {
 struct Y {};
-}
-}
+} // namespace ns
+} // namespace reopen
 
 namespace qualified {
 // CHECK: NamespaceDecl [[QV1:0x[^ ]+]] {{.*}} v1 inline
 inline namespace v1 {
 // CHECK: CXXRecordDecl [[X:0x[^ ]+]] {{.*}} struct X
 struct X;
-}
-}
+} // namespace v1
+} // namespace qualified
 namespace alias = qualified;
 // CHECK: CXXRecordDecl {{.*}} parent [[QV1]] prev [[X]] {{.*}} struct X 
definition
 struct alias::X {};
@@ -32,8 +32,8 @@ struct alias::X {};
 namespace ordinary {
 // CHECK: NamespaceDecl [[NESTED:0x[^ ]+]] {{.*}} nested
 namespace nested {}
-}
+} // namespace ordinary
 namespace ordinary {
 // CHECK: NamespaceDecl {{0x[^ ]+}} prev [[NESTED]] {{.*}} nested
 namespace nested {}
-}
+} // namespace ordinary
diff --git a/clang/test/CodeGenCXX/mangle-inline-namespace-redeclarations.cpp 
b/clang/test/CodeGenCXX/mangle-inline-namespace-redeclarations.cpp
index fe1f5dbe72f91a..9c7d9ae6a89dd5 100644
--- a/clang/test/CodeGenCXX/mangle-inline-namespace-redeclarations.cpp
+++ b/clang/test/CodeGenCXX/mangle-inline-namespace-redeclarations.cpp
@@ -7,8 +7,8 @@ namespace reopen {
 inline namespace v1 {
 namespace ns {
 struct Y;
-}
-}
+} // namespace ns
+} // namespace v1
 
 // CHECK-DAG: define {{.*}} @_ZN6reopen6beforeEPNS_2v12ns1YE(
 void before(v1::ns::Y *) {}
@@ -21,21 +21,21 @@ struct Y {};
 void f() {}
 // CHECK-DAG: @_ZN6reopen2v12ns5valueE =
 int value;
-}
+} // namespace ns
 
 // CHECK-DAG: define {{.*}} @_ZN6reopen5afterEPNS_2v12ns1YE(
 void after(v1::ns::Y *) {}
 // CHECK-DAG: define {{.*}} @_ZN6reopen4takeEPNS_2v12ns1XE(
 void take(v1::ns::X *) {}
-}
+} // namespace reopen
 
 // A qualified class definition can find its declaration through an inline
 // namespace, as in libstdc++'s filesystem::_Dir definition.
 namespace qualified {
 inline namespace v1 {
 struct X;
-}
-}
+} // namespace v1
+} // namespace qualified
 namespace alias = qualified;
 
 // CHECK-DAG: define {{.*}} @_Z6beforePN9qualified2v11XE(
@@ -52,7 +52,7 @@ void after(alias::X *p) { p->member(); }
 // An ordinary namespace must keep its existing mangling.
 namespace ordinary {
 struct X;
-}
+} // namespace ordinary
 struct ordinary::X {};
 // CHECK-DAG: define {{.*}} @_Z4takePN8ordinary1XE(
 void take(ordinary::X *) {}
diff --git a/clang/test/SemaCXX/inline-namespace-redeclarations.cpp 
b/clang/test/SemaCXX/inline-namespace-redeclarations.cpp
index 6b6063f62da40d..bf9fae056e5304 100644
--- a/clang/test/SemaCXX/inline-namespace-redeclarations.cpp
+++ b/clang/test/SemaCXX/inline-namespace-redeclarations.cpp
@@ -9,11 +9,11 @@ constexpr int value = 0;
 inline namespace v1 {
 constexpr int value = 1;
 namespace ns {}
-}
+} // namespace v1
 namespace ns {
 static_assert(value == 1, "lookup in the inline namespace");
-}
-}
+} // namespace ns
+} // namespace reopen
 
 namespace qualified {
 constexpr int value = 0;
@@ -21,8 +21,8 @@ inline namespace v1 {
 constexpr int value = 1;
 struct X;
 enum class E;
-}
-}
+} // namespace v1
+} // namespace qualified
 namespace alias = qualified;
 struct alias::X {
   static_assert(value == 1, "lookup in the inline namespace");

>From a32ec6a23a338bafe3d06381116490e6babbbbfc Mon Sep 17 00:00:00 2001
From: Corentin Kerisit <[email protected]>
Date: Thu, 22 Oct 2026 09:53:27 +0200
Subject: [PATCH 4/4] [Clang] Extend CWG2061 coverage for inline namespace
 lookup

Check that reopening a namespace through its enclosing namespace retains lookup 
through the original inline namespace. The added assertion fails with ambiguous 
lookup on unmodified Clang and passes with the semantic-parent fix.
---
 clang/test/CXX/drs/cwg20xx.cpp | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/clang/test/CXX/drs/cwg20xx.cpp b/clang/test/CXX/drs/cwg20xx.cpp
index 5c23ef9c5240bf..236f9c70c47030 100644
--- a/clang/test/CXX/drs/cwg20xx.cpp
+++ b/clang/test/CXX/drs/cwg20xx.cpp
@@ -125,7 +125,9 @@ static_assert(__is_same(decltype(a), decltype(b)));
 namespace cwg2061 { // cwg2061: 2.7
 #if __cplusplus >= 201103L
   namespace A {
+    constexpr int value = 0;
     inline namespace b {
+      constexpr int value = 1;
       namespace C {
         // 'f' is the example from the DR.  'S' is an example where if we 
didn't
         // properly handle the two being the same, we would get an incomplete
@@ -138,6 +140,9 @@ namespace cwg2061 { // cwg2061: 2.7
 
   namespace A {
     namespace C {
+      // GH95154: reopening C must retain b as its semantic parent, so lookup
+      // finds b::value before considering A::value.
+      static_assert(value == 1, "lookup in the original inline namespace");
       template<> void f<int>() { }
       template<> struct S<int> { };
     }

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

Reply via email to