[clang] [C++20][Modules] Fix merging of anonymous members of class templates. (PR #155948)

Michael Park via cfe-commits cfe-commits at lists.llvm.org
Fri Sep 12 20:50:30 PDT 2025


https://github.com/mpark updated https://github.com/llvm/llvm-project/pull/155948

>From 63519ed11294db85a9896e33a6a34bbc8b067dde Mon Sep 17 00:00:00 2001
From: Michael Park <mcypark at gmail.com>
Date: Thu, 28 Aug 2025 14:38:21 -0700
Subject: [PATCH 1/2] [C++20][Modules] Add tests related to anonymous members
 in class templates.

---
 .../test/Modules/merge-anon-in-template-2.cpp | 47 +++++++++++++++++++
 .../test/Modules/merge-anon-in-template-3.cpp | 45 ++++++++++++++++++
 2 files changed, 92 insertions(+)
 create mode 100644 clang/test/Modules/merge-anon-in-template-2.cpp
 create mode 100644 clang/test/Modules/merge-anon-in-template-3.cpp

diff --git a/clang/test/Modules/merge-anon-in-template-2.cpp b/clang/test/Modules/merge-anon-in-template-2.cpp
new file mode 100644
index 0000000000000..15852f0120798
--- /dev/null
+++ b/clang/test/Modules/merge-anon-in-template-2.cpp
@@ -0,0 +1,47 @@
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+// RUN: split-file %s %t
+
+// RUN: %clang_cc1 -std=c++20 -fmodule-name=hu-01 -emit-header-unit -xc++-user-header %t/hu-01.h \
+// RUN:  -o %t/hu-01.pcm
+
+// RUN: %clang_cc1 -std=c++20 -fmodule-name=hu-02 -emit-header-unit -xc++-user-header %t/hu-02.h \
+// RUN:  -Wno-experimental-header-units \
+// RUN:  -fmodule-map-file=%t/hu-01.map -fmodule-file=hu-01=%t/hu-01.pcm \
+// RUN:  -o %t/hu-02.pcm
+
+// RUN: %clang_cc1 -std=c++20 -emit-obj %t/main.cpp \
+// RUN:  -Wno-experimental-header-units \
+// RUN:  -fmodule-map-file=%t/hu-01.map -fmodule-file=hu-01=%t/hu-01.pcm \
+// RUN:  -fmodule-map-file=%t/hu-02.map -fmodule-file=hu-02=%t/hu-02.pcm
+
+//--- hu-01.map
+module "hu-01" {
+  header "hu-01.h"
+  export *
+}
+
+//--- hu-02.map
+module "hu-02" {
+  header "hu-02.h"
+  export *
+}
+
+//--- hu-01.h
+template <typename T>
+struct S { union { T x; }; };
+
+using SI = S<int>;
+
+//--- hu-02.h
+template <typename T>
+struct S { union { T x; }; };
+
+inline void f(S<int> s = {}) { s.x; }
+
+//--- main.cpp
+import "hu-01.h";
+void g(S<int>) {}
+
+import "hu-02.h";
+void h() { f(); }
diff --git a/clang/test/Modules/merge-anon-in-template-3.cpp b/clang/test/Modules/merge-anon-in-template-3.cpp
new file mode 100644
index 0000000000000..1ee447e3e524d
--- /dev/null
+++ b/clang/test/Modules/merge-anon-in-template-3.cpp
@@ -0,0 +1,45 @@
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+// RUN: split-file %s %t
+
+// RUN: %clang_cc1 -std=c++20 -fmodule-name=hu-01 -emit-header-unit -xc++-user-header %t/hu-01.h \
+// RUN:  -o %t/hu-01.pcm
+
+// RUN: %clang_cc1 -std=c++20 -fmodule-name=hu-02 -emit-header-unit -xc++-user-header %t/hu-02.h \
+// RUN:  -Wno-experimental-header-units \
+// RUN:  -fmodule-map-file=%t/hu-01.map -fmodule-file=hu-01=%t/hu-01.pcm \
+// RUN:  -o %t/hu-02.pcm
+
+// RUN: %clang_cc1 -std=c++20 -emit-obj %t/main.cpp \
+// RUN:  -Wno-experimental-header-units \
+// RUN:  -fmodule-map-file=%t/hu-01.map -fmodule-file=hu-01=%t/hu-01.pcm \
+// RUN:  -fmodule-map-file=%t/hu-02.map -fmodule-file=hu-02=%t/hu-02.pcm
+
+//--- hu-01.map
+module "hu-01" {
+  header "hu-01.h"
+  export *
+}
+
+//--- hu-02.map
+module "hu-02" {
+  header "hu-02.h"
+  export *
+}
+
+//--- hu-01.h
+template <typename T>
+struct S { union { T x; }; };
+
+using SI = S<int>;
+
+//--- hu-02.h
+import "hu-01.h";
+inline void f(S<int> s = {}) { s.x; }
+
+//--- main.cpp
+import "hu-01.h";
+void g(S<int>) {}
+
+import "hu-02.h";
+void h() { f(); }

>From 302a2ea3cd891fb975cb2e5e20f4595552b7e2e9 Mon Sep 17 00:00:00 2001
From: Michael Park <mcypark at gmail.com>
Date: Fri, 12 Sep 2025 20:37:42 -0700
Subject: [PATCH 2/2] [C++20][Modules] Number the anonymous members if the decl
 was instantiated locally.

---
 clang/include/clang/AST/DeclTemplate.h     |  9 +++++++++
 clang/lib/AST/DeclTemplate.cpp             |  5 +++--
 clang/lib/Sema/SemaTemplateInstantiate.cpp |  1 +
 clang/lib/Serialization/ASTReaderDecl.cpp  | 11 ++++++++++-
 4 files changed, 23 insertions(+), 3 deletions(-)

diff --git a/clang/include/clang/AST/DeclTemplate.h b/clang/include/clang/AST/DeclTemplate.h
index bba72365089f9..0a2cc18b00efa 100644
--- a/clang/include/clang/AST/DeclTemplate.h
+++ b/clang/include/clang/AST/DeclTemplate.h
@@ -1874,6 +1874,11 @@ class ClassTemplateSpecializationDecl : public CXXRecordDecl,
   LLVM_PREFERRED_TYPE(bool)
   unsigned StrictPackMatch : 1;
 
+  /// Indicate whether this instantiation was performed locally (as opposed to
+  /// performed externally, during a module precompile).
+  LLVM_PREFERRED_TYPE(bool)
+  unsigned LocalInstantiation : 1;
+
 protected:
   ClassTemplateSpecializationDecl(ASTContext &Context, Kind DK, TagKind TK,
                                   DeclContext *DC, SourceLocation StartLoc,
@@ -1968,6 +1973,10 @@ class ClassTemplateSpecializationDecl : public CXXRecordDecl,
 
   void setStrictPackMatch(bool Val) { StrictPackMatch = Val; }
 
+  bool isLocalInstantiation() { return LocalInstantiation; }
+
+  void setLocalInstantiation() { LocalInstantiation = true; }
+
   /// Get the point of instantiation (if any), or null if none.
   SourceLocation getPointOfInstantiation() const {
     return PointOfInstantiation;
diff --git a/clang/lib/AST/DeclTemplate.cpp b/clang/lib/AST/DeclTemplate.cpp
index 3162857aac5d0..cfb845a1728f8 100644
--- a/clang/lib/AST/DeclTemplate.cpp
+++ b/clang/lib/AST/DeclTemplate.cpp
@@ -974,7 +974,8 @@ ClassTemplateSpecializationDecl::ClassTemplateSpecializationDecl(
                     SpecializedTemplate->getIdentifier(), PrevDecl),
       SpecializedTemplate(SpecializedTemplate),
       TemplateArgs(TemplateArgumentList::CreateCopy(Context, Args)),
-      SpecializationKind(TSK_Undeclared), StrictPackMatch(StrictPackMatch) {
+      SpecializationKind(TSK_Undeclared), StrictPackMatch(StrictPackMatch),
+      LocalInstantiation(false) {
   assert(DK == Kind::ClassTemplateSpecialization || StrictPackMatch == false);
 }
 
@@ -982,7 +983,7 @@ ClassTemplateSpecializationDecl::ClassTemplateSpecializationDecl(ASTContext &C,
                                                                  Kind DK)
     : CXXRecordDecl(DK, TagTypeKind::Struct, C, nullptr, SourceLocation(),
                     SourceLocation(), nullptr, nullptr),
-      SpecializationKind(TSK_Undeclared) {}
+      SpecializationKind(TSK_Undeclared), LocalInstantiation(false)  {}
 
 ClassTemplateSpecializationDecl *ClassTemplateSpecializationDecl::Create(
     ASTContext &Context, TagKind TK, DeclContext *DC, SourceLocation StartLoc,
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index a72c95d6d77cf..cd3bcc1fc6537 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -3699,6 +3699,7 @@ Sema::InstantiateClass(SourceLocation PointOfInstantiation,
         = dyn_cast<ClassTemplateSpecializationDecl>(Instantiation)) {
     Spec->setTemplateSpecializationKind(TSK);
     Spec->setPointOfInstantiation(PointOfInstantiation);
+    Spec->setLocalInstantiation();
   }
 
   InstantiatingTemplate Inst(*this, PointOfInstantiation, Instantiation);
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index 6b35b205079e5..a40d073f32ea0 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -3425,7 +3425,16 @@ NamedDecl *ASTDeclReader::getAnonymousDeclForMerging(ASTReader &Reader,
   // If this is the first time, but we have parsed a declaration of the context,
   // build the anonymous declaration list from the parsed declaration.
   auto *PrimaryDC = getPrimaryDCForAnonymousDecl(DC);
-  if (PrimaryDC && !cast<Decl>(PrimaryDC)->isFromASTFile()) {
+  auto needToNumberAnonymousDeclsWithin = [](Decl *D) {
+    if (!D->isFromASTFile())
+      return true;
+    // If this is a class template specialization from an AST file
+    // but the instantiation occurred locally, we still need to number
+    // the anonymous decls.
+    auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(D);
+    return CTSD && CTSD->isLocalInstantiation();
+  };
+  if (PrimaryDC && needToNumberAnonymousDeclsWithin(cast<Decl>(PrimaryDC))) {
     numberAnonymousDeclsWithin(PrimaryDC, [&](NamedDecl *ND, unsigned Number) {
       if (Previous.size() == Number)
         Previous.push_back(cast<NamedDecl>(ND->getCanonicalDecl()));



More information about the cfe-commits mailing list