https://github.com/ChuanqiXu9 created 
https://github.com/llvm/llvm-project/pull/219094

Close https://github.com/llvm/llvm-project/issues/218228

The root cause of the problem is the corresponding friend is not loaded at the 
point of ADL.

This patch tries to fix this simply by loading the friends at the point of ADL. 
Note that this may be best efficient if there are a lot of friends. We just 
think it is rare. If it is really possible, we can change the structure of 
friends from a list to a name lookup table.

>From 53487b2ca66ec63a92621387f1904580bc02c220 Mon Sep 17 00:00:00 2001
From: Chuanqi Xu <[email protected]>
Date: Thu, 27 Aug 2026 10:45:28 +0800
Subject: [PATCH] [C++20] [Modules] Load friends for classes in ADL

Close https://github.com/llvm/llvm-project/issues/218228

The root cause of the problem is the corresponding friend
is not loaded at the point of ADL.

This patch tries to fix this simply by loading the friends
at the point of ADL. Note that this may be best efficient
if there are a lot of friends. We just think it is rare.
If it is really possible, we can change the structure of friends
from a list to a name lookup table.
---
 clang/include/clang/AST/DeclCXX.h |  4 ++
 clang/lib/AST/DeclFriend.cpp      |  9 +++
 clang/lib/Sema/SemaLookup.cpp     | 11 ++++
 clang/test/Modules/pr218228.cppm  | 95 +++++++++++++++++++++++++++++++
 4 files changed, 119 insertions(+)
 create mode 100644 clang/test/Modules/pr218228.cppm

diff --git a/clang/include/clang/AST/DeclCXX.h 
b/clang/include/clang/AST/DeclCXX.h
index a42884be71d68..afe46fae1bceb 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -692,6 +692,10 @@ class CXXRecordDecl : public RecordDecl {
     return data().FirstFriend.isValid();
   }
 
+  bool hasLazyFriends() const { return data().FirstFriend.isOffset(); }
+
+  void loadLazyFriends();
+
   /// \c true if a defaulted copy constructor for this class would be
   /// deleted.
   bool defaultedCopyConstructorIsDeleted() const {
diff --git a/clang/lib/AST/DeclFriend.cpp b/clang/lib/AST/DeclFriend.cpp
index d730b4f00fba2..7fd5631ed1343 100644
--- a/clang/lib/AST/DeclFriend.cpp
+++ b/clang/lib/AST/DeclFriend.cpp
@@ -60,6 +60,15 @@ FriendDecl *CXXRecordDecl::getFirstFriend() const {
   return First ? cast<FriendDecl>(First) : nullptr;
 }
 
+void CXXRecordDecl::loadLazyFriends() {
+  assert(hasDefinition());
+  assert(hasLazyFriends());
+  ExternalASTSource *Source = getParentASTContext().getExternalSource();
+  FriendDecl *Friend = 
cast_or_null<FriendDecl>(data().FirstFriend.get(Source));
+  while (Friend && Friend->NextFriend.isOffset())
+    Friend = cast_or_null<FriendDecl>(Friend->NextFriend.get(Source));
+}
+
 SourceRange FriendDecl::getSourceRange() const {
   if (TypeSourceInfo *TInfo = getFriendType()) {
     SourceLocation EndL =
diff --git a/clang/lib/Sema/SemaLookup.cpp b/clang/lib/Sema/SemaLookup.cpp
index 43129800e9813..fe2763213280f 100644
--- a/clang/lib/Sema/SemaLookup.cpp
+++ b/clang/lib/Sema/SemaLookup.cpp
@@ -3881,6 +3881,17 @@ void Sema::ArgumentDependentLookup(DeclarationName Name, 
SourceLocation Loc,
                                      AssociatedNamespaces,
                                      AssociatedClasses);
 
+  // Load the friend classes in case there are unloaded decls.
+  //
+  // FIXME: Currently this is inefficient if there are a lot of friends
+  // in the classes. We just expect the number of friends are limited
+  // in real world. In case we meet the case that the loading friends
+  // became a threshold, we can change the structure of friends from
+  // a list to a name lookup table.
+  for (CXXRecordDecl *Class : AssociatedClasses)
+    if (Class->hasDefinition() && Class->hasLazyFriends())
+      Class->loadLazyFriends();
+
   // C++ [basic.lookup.argdep]p3:
   //   Let X be the lookup set produced by unqualified lookup (3.4.1)
   //   and let Y be the lookup set produced by argument dependent
diff --git a/clang/test/Modules/pr218228.cppm b/clang/test/Modules/pr218228.cppm
new file mode 100644
index 0000000000000..b00ed95f465cf
--- /dev/null
+++ b/clang/test/Modules/pr218228.cppm
@@ -0,0 +1,95 @@
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+// RUN: split-file %s %t
+
+// RUN: %clang_cc1 -std=c++23 -emit-module-interface %t/stdx.cc -o %t/stdx.pcm
+// RUN: %clang_cc1 -std=c++23 -emit-module-interface %t/a.cppm -o %t/a.pcm 
-fmodule-file=stdx=%t/stdx.pcm
+// RUN: %clang_cc1 -std=c++23 %t/b.cppm -fmodule-file=stdx=%t/stdx.pcm 
-fmodule-file=a=%t/a.pcm -fsyntax-only -verify
+// RUN: %clang_cc1 -std=c++23 %t/c.cpp -fmodule-file=stdx=%t/stdx.pcm 
-fmodule-file=a=%t/a.pcm -fsyntax-only -verify
+// RUN: %clang_cc1 -std=c++23 %t/d.cpp -fmodule-file=stdx=%t/stdx.pcm 
-fmodule-file=a=%t/a.pcm -fsyntax-only -verify
+
+// Test again with Reduced BMI
+// RUN: %clang_cc1 -std=c++23 -emit-reduced-module-interface %t/stdx.cc -o 
%t/stdx.pcm
+// RUN: %clang_cc1 -std=c++23 -emit-reduced-module-interface %t/a.cppm -o 
%t/a.pcm -fmodule-file=stdx=%t/stdx.pcm
+// RUN: %clang_cc1 -std=c++23 %t/b.cppm -fmodule-file=stdx=%t/stdx.pcm 
-fmodule-file=a=%t/a.pcm -fsyntax-only -verify
+// RUN: %clang_cc1 -std=c++23 %t/c.cpp -fmodule-file=stdx=%t/stdx.pcm 
-fmodule-file=a=%t/a.pcm -fsyntax-only -verify
+// RUN: %clang_cc1 -std=c++23 %t/d.cpp -fmodule-file=stdx=%t/stdx.pcm 
-fmodule-file=a=%t/a.pcm -fsyntax-only -verify
+
+//--- pipe.hh
+namespace demo {
+
+template <typename Derived>
+struct RAC {
+  template <typename R, typename Self>
+  friend constexpr auto operator|(R&&, Self&&);
+};
+
+template <typename A, typename Arg>
+struct P : RAC<P<A, Arg>> {
+  template <typename R>
+  constexpr auto operator()(R&& r) const {
+    return A{}(static_cast<R&&>(r));
+  }
+};
+
+struct myadapt {
+  template <class... Args>
+  constexpr auto operator()(Args&&...) const {
+    return P<myadapt, Args...>{};
+  }
+};
+
+template <typename R, typename Self>
+constexpr auto operator|(R&& r, Self&& self) {
+  return static_cast<Self&&>(self)(static_cast<R&&>(r));
+}
+
+inline constexpr myadapt adapt{};
+
+} // namespace demo
+
+//--- stdx.cc
+module;
+
+#include "pipe.hh"
+
+export module stdx;
+
+export namespace demo {
+
+using demo::myadapt;
+using demo::adapt;
+
+// To avoid another GCC-only problem
+inline void force() { struct X{}; (void)(X{} | demo::adapt(1)); }
+
+} // namespace demo
+
+//--- a.cppm
+export module a;
+import stdx;
+void g() { (void) demo::adapt('.'); }
+
+//--- b.cppm
+// expected-no-diagnostics
+export module b;
+import stdx;
+import a;
+struct view {};
+void f() { (void)(view{} | demo::adapt('.')); }
+
+//--- c.cpp
+// expected-no-diagnostics
+import stdx;
+import a;
+#include "pipe.hh"
+struct view {};
+void f() { (void)(view{} | demo::adapt('.')); }
+
+//--- d.cpp
+// expected-no-diagnostics
+#include "pipe.hh"
+import stdx;
+import a;
+struct view {};
+void f() { (void)(view{} | demo::adapt('.')); }

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

Reply via email to