https://github.com/zygoloid created 
https://github.com/llvm/llvm-project/pull/210178

Add an accessor to walk the fields and indirect fields of a class without 
deserializing everything, and use it instead of walking over all of `decls()` 
in a couple of places within the implicit definition of a special member 
function. We already have machinery to only import those parts of the class 
without importing everything else, but we weren't using it here.

This allows us to use classes with implicit special members from an imported 
PCH or module without importing all the declarations in the class.

>From 2dea40bfd6c194b21d64402e18860c0941400a92 Mon Sep 17 00:00:00 2001
From: Richard Smith <[email protected]>
Date: Thu, 16 Jul 2026 20:46:33 +0000
Subject: [PATCH] Don't eagerly load *all* declarations in a class when
 defining a special member funciton.

---
 clang/include/clang/AST/Decl.h                | 22 +++++++++++++++++
 clang/lib/AST/Decl.cpp                        | 24 +++++++++++++++----
 clang/lib/Sema/SemaDeclCXX.cpp                |  4 ++--
 .../test/OpenMP/declare_target_ast_print.cpp  | 14 ++++++-----
 clang/test/PCH/Inputs/cxx-method.h            |  7 ++++++
 clang/test/PCH/cxx-method.cpp                 | 18 ++++++++++++--
 6 files changed, 75 insertions(+), 14 deletions(-)

diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index 0a6f256afa2cc..64d39caec2932 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -410,6 +410,10 @@ class NamedDecl : public Decl {
   /// a C++ class.
   bool isCXXInstanceMember() const;
 
+  // Determine whether this declaration is a direct or indirect field. For use
+  // with filtered_decl_iterator.
+  bool isDirectOrIndirectFieldDecl() const;
+
   /// Determine if the declaration obeys the reserved identifier rules of the
   /// given language.
   ReservedIdentifierStatus isReserved(const LangOptions &LangOpts) const;
@@ -4603,6 +4607,24 @@ class RecordDecl : public TagDecl {
     return noload_field_begin() == noload_field_end();
   }
 
+  // Iterator access to direct or indirect field members. The field iterator
+  // only visits the non-static data members of this class, ignoring any static
+  // data members, functions, constructors, destructors, etc.
+  using direct_or_indirect_field_iterator =
+      filtered_decl_iterator<NamedDecl,
+                             &NamedDecl::isDirectOrIndirectFieldDecl>;
+  using direct_or_indirect_field_range =
+      llvm::iterator_range<direct_or_indirect_field_iterator>;
+
+  direct_or_indirect_field_range direct_or_indirect_fields() const {
+    return direct_or_indirect_field_range(direct_or_indirect_field_begin(),
+                                          direct_or_indirect_field_end());
+  }
+  direct_or_indirect_field_iterator direct_or_indirect_field_begin() const;
+  direct_or_indirect_field_iterator direct_or_indirect_field_end() const {
+    return direct_or_indirect_field_iterator(decl_iterator());
+  }
+
   /// 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 4eaef0d87f3e5..002c0085e58a2 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -1128,6 +1128,14 @@ bool NamedDecl::isPlaceholderVar(const LangOptions 
&LangOpts) const {
   return false;
 }
 
+static bool isDirectOrIndirectFieldDeclKind(Decl::Kind K) {
+  return FieldDecl::classofKind(K) || IndirectFieldDecl::classofKind(K);
+}
+
+bool NamedDecl::isDirectOrIndirectFieldDecl() const {
+  return isDirectOrIndirectFieldDeclKind(getKind());
+}
+
 ReservedIdentifierStatus
 NamedDecl::isReserved(const LangOptions &LangOpts) const {
   const IdentifierInfo *II = getIdentifier();
@@ -5280,6 +5288,15 @@ RecordDecl::field_iterator RecordDecl::field_begin() 
const {
   return field_iterator(decl_iterator(FirstDecl));
 }
 
+RecordDecl::direct_or_indirect_field_iterator
+RecordDecl::direct_or_indirect_field_begin() const {
+  if (hasExternalLexicalStorage() && !hasLoadedFieldsFromExternalStorage())
+    LoadFieldsFromExternalStorage();
+  if (RecordDecl *D = getDefinition(); D && D != this)
+    return D->direct_or_indirect_field_begin();
+  return direct_or_indirect_field_iterator(decl_iterator(FirstDecl));
+}
+
 RecordDecl::field_iterator RecordDecl::noload_field_begin() const {
   return field_iterator(decl_iterator(getDefinitionOrSelf()->FirstDecl));
 }
@@ -5333,14 +5350,13 @@ void RecordDecl::LoadFieldsFromExternalStorage() const {
 
   SmallVector<Decl*, 64> Decls;
   setHasLoadedFieldsFromExternalStorage(true);
-  Source->FindExternalLexicalDecls(this, [](Decl::Kind K) {
-    return FieldDecl::classofKind(K) || IndirectFieldDecl::classofKind(K);
-  }, Decls);
+  Source->FindExternalLexicalDecls(this, isDirectOrIndirectFieldDeclKind,
+                                   Decls);
 
 #ifndef NDEBUG
   // Check that all decls we got were FieldDecls.
   for (unsigned i=0, e=Decls.size(); i != e; ++i)
-    assert(isa<FieldDecl>(Decls[i]) || isa<IndirectFieldDecl>(Decls[i]));
+    assert(cast<NamedDecl>(Decls[i])->isDirectOrIndirectFieldDecl());
 #endif
 
   if (Decls.empty())
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 37bb69da90b6c..738f667a74ed2 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -4127,7 +4127,7 @@ namespace {
     llvm::SmallPtrSet<ValueDecl*, 4> UninitializedFields;
 
     // At the beginning, all fields are uninitialized.
-    for (auto *I : RD->decls()) {
+    for (auto *I : RD->direct_or_indirect_fields()) {
       if (auto *FD = dyn_cast<FieldDecl>(I)) {
         UninitializedFields.insert(FD);
       } else if (auto *IFD = dyn_cast<IndirectFieldDecl>(I)) {
@@ -5586,7 +5586,7 @@ bool Sema::SetCtorInitializers(CXXConstructorDecl 
*Constructor, bool AnyErrors,
   }
 
   // Fields.
-  for (auto *Mem : ClassDecl->decls()) {
+  for (auto *Mem : ClassDecl->direct_or_indirect_fields()) {
     if (auto *F = dyn_cast<FieldDecl>(Mem)) {
       // C++ [class.bit]p2:
       //   A declaration for a bit-field that omits the identifier declares an
diff --git a/clang/test/OpenMP/declare_target_ast_print.cpp 
b/clang/test/OpenMP/declare_target_ast_print.cpp
index 7b63c15dd455e..2e5b92b4785be 100644
--- a/clang/test/OpenMP/declare_target_ast_print.cpp
+++ b/clang/test/OpenMP/declare_target_ast_print.cpp
@@ -215,19 +215,17 @@ struct C {
 // CHECK-NEXT: #pragma omp end declare target
 
 // CHECK: template<> struct C<int>
-  T t;
-// CHECK-NEXT: int t;
   static T ts;
-// CHECK-NEXT: #pragma omp declare target
+// CHECK: #pragma omp declare target
 // CHECK-NEXT: static int ts;
-// CHECK: #pragma omp end declare target
+// CHECK-NEXT: #pragma omp end declare target
 
   C(T t) : t(t) {
   }
 // CHECK: #pragma omp declare target
 // CHECK-NEXT: C(int t) : t(t) {
 // CHECK-NEXT: }
-// CHECK: #pragma omp end declare target
+// CHECK-NEXT: #pragma omp end declare target
 
   T foo() {
     return t;
@@ -236,7 +234,11 @@ struct C {
 // CHECK-NEXT: int foo() {
 // CHECK-NEXT: return this->t;
 // CHECK-NEXT: }
-// CHECK: #pragma omp end declare target
+// CHECK-NEXT: #pragma omp end declare target
+
+// Not inside a #pragma omp declare target.
+// CHECK-NEXT: int t;
+  T t;
 };
 
 template<class T>
diff --git a/clang/test/PCH/Inputs/cxx-method.h 
b/clang/test/PCH/Inputs/cxx-method.h
index d5d56fed0585e..e6d271c00f888 100644
--- a/clang/test/PCH/Inputs/cxx-method.h
+++ b/clang/test/PCH/Inputs/cxx-method.h
@@ -3,7 +3,14 @@ struct S {
 
   S();
   S(const S&);
+  S &operator=(const S&);
+
+  void doNotDeserialize();
 
   operator const char*();
   operator char*();
 };
+
+struct Trivial {
+  void doNotDeserialize();
+};
\ No newline at end of file
diff --git a/clang/test/PCH/cxx-method.cpp b/clang/test/PCH/cxx-method.cpp
index c24ad9297588a..fff09b2bc4829 100644
--- a/clang/test/PCH/cxx-method.cpp
+++ b/clang/test/PCH/cxx-method.cpp
@@ -1,8 +1,22 @@
 // RUN: %clang_cc1 -x c++ -include %S/Inputs/cxx-method.h -verify %s
 // RUN: %clang_cc1 -x c++ -emit-pch %S/Inputs/cxx-method.h -o %t
-// RUN: %clang_cc1 -include-pch %t -verify %s
+// RUN: %clang_cc1 -include-pch %t -verify %s -error-on-deserialized-decl 
doNotDeserialize -ast-dump
 // expected-no-diagnostics
 
+// Calling constructors should not cause doNotDeserialize to be deserialized.
+S s;
+S s2(s);
+
+// Implicitly defining special member functions should not cause
+// doNotDeserialize to be deserialized.
+Trivial t;
+Trivial t2(t);
+
+void assign() {
+  s = s2;
+  t = t2;
+}
+
 void S::m(int x) { }
 
 S::operator char *() { return 0; }
@@ -12,4 +26,4 @@ S::operator const char *() { return 0; }
 struct T : S {};
 
 const T a = T();
-T b(a);
+T b(a);
\ No newline at end of file

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

Reply via email to