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
