[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
Thu Sep 18 12:12:41 PDT 2025


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

>From 0e8b6420ec9b1060dbc9fa78d299f5bc6d2d002a 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 f04da44c9c629fd01e835cf72cadf4f56228dc34 Mon Sep 17 00:00:00 2001
From: Michael Park <mcypark at gmail.com>
Date: Wed, 17 Sep 2025 21:26:32 -0700
Subject: [PATCH 2/2] [C++20][Modules] Refine the condition of in-place
 numbering of anonymous members.

---
 clang/include/clang/AST/Decl.h            | 15 +++++++++++++++
 clang/lib/AST/Decl.cpp                    |  8 ++++++++
 clang/lib/Serialization/ASTReaderDecl.cpp | 14 +++++++++++++-
 3 files changed, 36 insertions(+), 1 deletion(-)

diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index c24d4124d9fc5..f79dea97dee79 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -4521,6 +4521,21 @@ class RecordDecl : public TagDecl {
     return field_begin() == field_end();
   }
 
+  /// noload_fields - Iterate over the fields stored in this record
+  /// that are currently loaded; don't attempt to retrieve anything
+  /// from an external source.
+  field_range noload_fields() const {
+    return field_range(noload_field_begin(), noload_field_end());
+  }
+
+  field_iterator noload_field_begin() const;
+  field_iterator noload_field_end() const { return field_iterator(decl_iterator()); }
+
+  // Whether there are any fields (non-static data members) in this record.
+  bool noload_field_empty() const {
+    return noload_field_begin() == noload_field_end();
+  }
+
   /// Note that the definition of this type is now complete.
   virtual void completeDefinition();
 
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index d8dffb7f5dc43..48e6b22cc65bd 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -5161,6 +5161,14 @@ RecordDecl::field_iterator RecordDecl::field_begin() const {
   return field_iterator(decl_iterator(FirstDecl));
 }
 
+RecordDecl::field_iterator RecordDecl::noload_field_begin() const {
+  // This is necessary for correctness for C++ with modules.
+  // FIXME: Come up with a test case that breaks without definition.
+  if (RecordDecl *D = getDefinition(); D && D != this)
+    return D->noload_field_begin();
+  return field_iterator(decl_iterator(FirstDecl));
+}
+
 /// completeDefinition - Notes that the definition of this type is now
 /// complete.
 void RecordDecl::completeDefinition() {
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index 4356f2b734fb0..cf32d4f56b7c2 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -3432,7 +3432,19 @@ 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, has at least
+    // one field, but none of the fields have been loaded from external storage,
+    // this is a situation where the class template specialization decl
+    // was imported but the definition was instantiated within the source.
+    // In such a case, we still need to number the anonymous decls.
+    auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(D);
+    return CTSD && !CTSD->noload_field_empty() &&
+           !CTSD->hasLoadedFieldsFromExternalStorage();
+  };
+  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