[clang] [Clang][ASTImporter] Fix cycle in importing template specialization on auto type with typename (PR #162514)

via cfe-commits cfe-commits at lists.llvm.org
Tue Oct 14 07:48:10 PDT 2025


https://github.com/ganenkokb-yandex updated https://github.com/llvm/llvm-project/pull/162514

>From 63e609960bc477fa516dac9b707a767e3719cbb3 Mon Sep 17 00:00:00 2001
From: Konstantin Ganenko <ganenkokb at yandex-team.ru>
Date: Tue, 7 Oct 2025 14:36:49 +0300
Subject: [PATCH 1/8] Fix cycle in importing template specialization on auto
 type

---
 clang/include/clang/AST/ASTImporter.h   |  1 +
 clang/lib/AST/ASTImporter.cpp           |  9 ++++-
 clang/unittests/AST/ASTImporterTest.cpp | 50 +++++++++++++++++++++++++
 3 files changed, 59 insertions(+), 1 deletion(-)

diff --git a/clang/include/clang/AST/ASTImporter.h b/clang/include/clang/AST/ASTImporter.h
index 4a0ca45b785a9..eea4ccccb1600 100644
--- a/clang/include/clang/AST/ASTImporter.h
+++ b/clang/include/clang/AST/ASTImporter.h
@@ -254,6 +254,7 @@ class TypeSourceInfo;
     /// Declaration (from, to) pairs that are known not to be equivalent
     /// (which we have already complained about).
     NonEquivalentDeclSet NonEquivalentDecls;
+    llvm::DenseSet<const Decl *> DeclTypeCycles;
 
     using FoundDeclsTy = SmallVector<NamedDecl *, 2>;
     FoundDeclsTy findDeclsInToCtx(DeclContext *DC, DeclarationName Name);
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index f43fa8c90ad3b..0dc2e1c3b4f8b 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -4035,7 +4035,8 @@ ExpectedDecl ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) {
     // E.g.: auto foo() { struct X{}; return X(); }
     // To avoid an infinite recursion when importing, create the FunctionDecl
     // with a simplified return type.
-    if (hasReturnTypeDeclaredInside(D)) {
+    if (hasReturnTypeDeclaredInside(D) ||
+      Importer.DeclTypeCycles.find(D) != Importer.DeclTypeCycles.end()) {
       FromReturnTy = Importer.getFromContext().VoidTy;
       UsedDifferentProtoType = true;
     }
@@ -4058,7 +4059,13 @@ ExpectedDecl ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) {
   }
 
   Error Err = Error::success();
+  if (!UsedDifferentProtoType) {
+    Importer.DeclTypeCycles.insert(D);
+  }
   auto T = importChecked(Err, FromTy);
+  if (!UsedDifferentProtoType) {
+    Importer.DeclTypeCycles.erase(D);
+  }
   auto TInfo = importChecked(Err, FromTSI);
   auto ToInnerLocStart = importChecked(Err, D->getInnerLocStart());
   auto ToEndLoc = importChecked(Err, D->getEndLoc());
diff --git a/clang/unittests/AST/ASTImporterTest.cpp b/clang/unittests/AST/ASTImporterTest.cpp
index e7160bcf2e0c2..5f7fcdf817ea0 100644
--- a/clang/unittests/AST/ASTImporterTest.cpp
+++ b/clang/unittests/AST/ASTImporterTest.cpp
@@ -3204,6 +3204,56 @@ TEST_P(ImportExpr, UnresolvedMemberExpr) {
                  compoundStmt(has(callExpr(has(unresolvedMemberExpr())))))))));
 }
 
+TEST_P(ImportExpr, CycleInAutoTemplateSpec) {
+  MatchVerifier<Decl> Verifier;
+  const char *Code = R"(
+  template <class _CharT>
+  struct basic_string {
+    using value_type = _CharT;
+  };
+
+  template<typename T>
+  struct basic_string_view {
+    using value_type = T;
+  };
+
+  using string_view = basic_string_view<char>;
+  using string = basic_string<char>;
+
+  template<typename T>
+  struct span {
+  };
+
+  template <typename StringT>
+  auto StrCatT(span<const StringT> pieces) {
+    basic_string<typename StringT::value_type> result;
+    return result;
+  }
+
+  string StrCat(span<const string_view> pieces) {
+      return StrCatT(pieces);
+  }
+
+  string StrCat(span<const string> pieces) {
+      return StrCatT(pieces);
+  }
+
+  template <typename T>
+  auto declToImport(T pieces) {
+    return StrCat(pieces);
+  }
+
+  void test() {
+    span<const string> pieces;
+    auto result = declToImport(pieces);
+  }
+)";
+  // This test reproduces the StrCatT recursion pattern with concepts and span
+  // that may cause infinite recursion during AST import due to circular dependencies
+  testImport(Code, Lang_CXX20, "", Lang_CXX20, Verifier,
+             functionTemplateDecl(hasName("declToImport")));
+}
+
 TEST_P(ImportExpr, ConceptNoRequirement) {
   MatchVerifier<Decl> Verifier;
   const char *Code = R"(

>From 3e2323025a1e64265adf587c2191d244506640dd Mon Sep 17 00:00:00 2001
From: Konstantin Ganenko <ganenkokb at yandex-team.ru>
Date: Mon, 13 Oct 2025 15:07:32 +0300
Subject: [PATCH 2/8] clang format + code review

---
 clang/lib/AST/ASTImporter.cpp           | 8 +++-----
 clang/unittests/AST/ASTImporterTest.cpp | 3 ++-
 2 files changed, 5 insertions(+), 6 deletions(-)

diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index 0dc2e1c3b4f8b..051a633cb99fa 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -4036,7 +4036,7 @@ ExpectedDecl ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) {
     // To avoid an infinite recursion when importing, create the FunctionDecl
     // with a simplified return type.
     if (hasReturnTypeDeclaredInside(D) ||
-      Importer.DeclTypeCycles.find(D) != Importer.DeclTypeCycles.end()) {
+        Importer.DeclTypeCycles.find(D) != Importer.DeclTypeCycles.end()) {
       FromReturnTy = Importer.getFromContext().VoidTy;
       UsedDifferentProtoType = true;
     }
@@ -4059,13 +4059,11 @@ ExpectedDecl ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) {
   }
 
   Error Err = Error::success();
-  if (!UsedDifferentProtoType) {
+  if (!UsedDifferentProtoType)
     Importer.DeclTypeCycles.insert(D);
-  }
   auto T = importChecked(Err, FromTy);
-  if (!UsedDifferentProtoType) {
+  if (!UsedDifferentProtoType)
     Importer.DeclTypeCycles.erase(D);
-  }
   auto TInfo = importChecked(Err, FromTSI);
   auto ToInnerLocStart = importChecked(Err, D->getInnerLocStart());
   auto ToEndLoc = importChecked(Err, D->getEndLoc());
diff --git a/clang/unittests/AST/ASTImporterTest.cpp b/clang/unittests/AST/ASTImporterTest.cpp
index 5f7fcdf817ea0..ab63ddeb5e94b 100644
--- a/clang/unittests/AST/ASTImporterTest.cpp
+++ b/clang/unittests/AST/ASTImporterTest.cpp
@@ -3249,7 +3249,8 @@ TEST_P(ImportExpr, CycleInAutoTemplateSpec) {
   }
 )";
   // This test reproduces the StrCatT recursion pattern with concepts and span
-  // that may cause infinite recursion during AST import due to circular dependencies
+  // that may cause infinite recursion during AST import due to circular
+  // dependencies
   testImport(Code, Lang_CXX20, "", Lang_CXX20, Verifier,
              functionTemplateDecl(hasName("declToImport")));
 }

>From 416fbca5730acb09c1d242458d415773f75f61d7 Mon Sep 17 00:00:00 2001
From: Konstantin Ganenko <ganenkokb at yandex-team.ru>
Date: Mon, 13 Oct 2025 16:24:09 +0300
Subject: [PATCH 3/8] Code review

Add comments for new member.
Make RAII approach for cycles monitoring.
---
 clang/include/clang/AST/ASTImporter.h | 13 ++++++-
 clang/lib/AST/ASTImporter.cpp         | 52 +++++++++++++++++++++++----
 2 files changed, 58 insertions(+), 7 deletions(-)

diff --git a/clang/include/clang/AST/ASTImporter.h b/clang/include/clang/AST/ASTImporter.h
index eea4ccccb1600..d5ad014bd9877 100644
--- a/clang/include/clang/AST/ASTImporter.h
+++ b/clang/include/clang/AST/ASTImporter.h
@@ -190,6 +190,8 @@ class TypeSourceInfo;
       llvm::SmallDenseMap<Decl *, int, 32> Aux;
     };
 
+    class FunctionReturnTypeDeclCycleDetector;
+
   private:
     std::shared_ptr<ASTImporterSharedState> SharedState = nullptr;
 
@@ -254,7 +256,16 @@ class TypeSourceInfo;
     /// Declaration (from, to) pairs that are known not to be equivalent
     /// (which we have already complained about).
     NonEquivalentDeclSet NonEquivalentDecls;
-    llvm::DenseSet<const Decl *> DeclTypeCycles;
+    // When template function return type is auto and return type is declared as
+    // typename from template params, there could be cycles in function
+    // importing when function decaration is still the need for return type
+    // declaration import. We have code path for nested types inside function
+    // (see hasReturnTypeDeclaredInside): assuming return type as VoidTy and
+    // calculate it later under UsedDifferentProtoType boolean. This class is
+    // reuse of this approach and make logic lazy - detect cycle - calculate
+    // return type later on.
+    std::unique_ptr<FunctionReturnTypeDeclCycleDetector>
+        FunctionReturnTypeCycleDetector;
 
     using FoundDeclsTy = SmallVector<NamedDecl *, 2>;
     FoundDeclsTy findDeclsInToCtx(DeclContext *DC, DeclarationName Name);
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index 051a633cb99fa..bcf8c19fb9391 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -1287,6 +1287,44 @@ bool ASTNodeImporter::hasSameVisibilityContextAndLinkage(TypedefNameDecl *Found,
 
 using namespace clang;
 
+class ASTImporter::FunctionReturnTypeDeclCycleDetector {
+public:
+  class ScopedReturnTypeImport {
+  public:
+    // Do not track cycles on D == nullptr.
+    ScopedReturnTypeImport(FunctionReturnTypeDeclCycleDetector &owner,
+                           const Decl *D)
+        : CycleDetector(owner), D(D) {
+      if (D)
+        CycleDetector.FunctionReturnTypeDeclCycles.insert(D);
+    }
+    ~ScopedReturnTypeImport() {
+      if (D)
+        CycleDetector.FunctionReturnTypeDeclCycles.erase(D);
+    }
+    ScopedReturnTypeImport(const ScopedReturnTypeImport &) = delete;
+    ScopedReturnTypeImport &operator=(const ScopedReturnTypeImport &) = delete;
+
+  private:
+    FunctionReturnTypeDeclCycleDetector &CycleDetector;
+    const Decl *D;
+  };
+
+  ScopedReturnTypeImport DetectImportCycles(const Decl *D) {
+    if (!IsCycle(D))
+      return ScopedReturnTypeImport(*this, D);
+    return ScopedReturnTypeImport(*this, nullptr);
+  }
+
+  bool IsCycle(const Decl *D) const {
+    return FunctionReturnTypeDeclCycles.find(D) !=
+           FunctionReturnTypeDeclCycles.end();
+  }
+
+private:
+  llvm::DenseSet<const Decl *> FunctionReturnTypeDeclCycles;
+};
+
 ExpectedType ASTNodeImporter::VisitType(const Type *T) {
   Importer.FromDiag(SourceLocation(), diag::err_unsupported_ast_node)
     << T->getTypeClassName();
@@ -4035,8 +4073,10 @@ ExpectedDecl ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) {
     // E.g.: auto foo() { struct X{}; return X(); }
     // To avoid an infinite recursion when importing, create the FunctionDecl
     // with a simplified return type.
+    // Reuse this approach for auto return types declared as typenames from
+    // template pamams, tracked in FunctionReturnTypeCycleDetector.
     if (hasReturnTypeDeclaredInside(D) ||
-        Importer.DeclTypeCycles.find(D) != Importer.DeclTypeCycles.end()) {
+        Importer.FunctionReturnTypeCycleDetector->IsCycle(D)) {
       FromReturnTy = Importer.getFromContext().VoidTy;
       UsedDifferentProtoType = true;
     }
@@ -4059,11 +4099,9 @@ ExpectedDecl ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) {
   }
 
   Error Err = Error::success();
-  if (!UsedDifferentProtoType)
-    Importer.DeclTypeCycles.insert(D);
+  auto ScopedReturnTypeDeclCycleDetector =
+      Importer.FunctionReturnTypeCycleDetector->DetectImportCycles(D);
   auto T = importChecked(Err, FromTy);
-  if (!UsedDifferentProtoType)
-    Importer.DeclTypeCycles.erase(D);
   auto TInfo = importChecked(Err, FromTSI);
   auto ToInnerLocStart = importChecked(Err, D->getInnerLocStart());
   auto ToEndLoc = importChecked(Err, D->getEndLoc());
@@ -9299,7 +9337,9 @@ ASTImporter::ASTImporter(ASTContext &ToContext, FileManager &ToFileManager,
                          std::shared_ptr<ASTImporterSharedState> SharedState)
     : SharedState(SharedState), ToContext(ToContext), FromContext(FromContext),
       ToFileManager(ToFileManager), FromFileManager(FromFileManager),
-      Minimal(MinimalImport), ODRHandling(ODRHandlingType::Conservative) {
+      Minimal(MinimalImport), ODRHandling(ODRHandlingType::Conservative),
+      FunctionReturnTypeCycleDetector(
+          std::make_unique<FunctionReturnTypeDeclCycleDetector>()) {
 
   // Create a default state without the lookup table: LLDB case.
   if (!SharedState) {

>From 087f9828ee9ec27cf8640acc74af49929fe10b05 Mon Sep 17 00:00:00 2001
From: Konstantin Ganenko <ganenkokb at yandex-team.ru>
Date: Mon, 13 Oct 2025 16:28:38 +0300
Subject: [PATCH 4/8] Code review

Use FunctionDecl straight forward.
---
 clang/lib/AST/ASTImporter.cpp | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index bcf8c19fb9391..d348bfd097eda 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -1293,7 +1293,7 @@ class ASTImporter::FunctionReturnTypeDeclCycleDetector {
   public:
     // Do not track cycles on D == nullptr.
     ScopedReturnTypeImport(FunctionReturnTypeDeclCycleDetector &owner,
-                           const Decl *D)
+                           const FunctionDecl *D)
         : CycleDetector(owner), D(D) {
       if (D)
         CycleDetector.FunctionReturnTypeDeclCycles.insert(D);
@@ -1307,22 +1307,22 @@ class ASTImporter::FunctionReturnTypeDeclCycleDetector {
 
   private:
     FunctionReturnTypeDeclCycleDetector &CycleDetector;
-    const Decl *D;
+    const FunctionDecl *D;
   };
 
-  ScopedReturnTypeImport DetectImportCycles(const Decl *D) {
+  ScopedReturnTypeImport DetectImportCycles(const FunctionDecl *D) {
     if (!IsCycle(D))
       return ScopedReturnTypeImport(*this, D);
     return ScopedReturnTypeImport(*this, nullptr);
   }
 
-  bool IsCycle(const Decl *D) const {
+  bool IsCycle(const FunctionDecl *D) const {
     return FunctionReturnTypeDeclCycles.find(D) !=
            FunctionReturnTypeDeclCycles.end();
   }
 
 private:
-  llvm::DenseSet<const Decl *> FunctionReturnTypeDeclCycles;
+  llvm::DenseSet<const FunctionDecl *> FunctionReturnTypeDeclCycles;
 };
 
 ExpectedType ASTNodeImporter::VisitType(const Type *T) {

>From 6d9a3a1d60a3e421e136422d9c3e12e52d92ac45 Mon Sep 17 00:00:00 2001
From: Konstantin Ganenko <ganenkokb at yandex-team.ru>
Date: Mon, 13 Oct 2025 19:27:21 +0300
Subject: [PATCH 5/8] Code review

Fix naming and comment
---
 clang/include/clang/AST/ASTImporter.h   | 13 +++++--------
 clang/lib/AST/ASTImporter.cpp           | 26 ++++++++++++-------------
 clang/unittests/AST/ASTImporterTest.cpp |  2 +-
 3 files changed, 19 insertions(+), 22 deletions(-)

diff --git a/clang/include/clang/AST/ASTImporter.h b/clang/include/clang/AST/ASTImporter.h
index d5ad014bd9877..3169b0074cc8d 100644
--- a/clang/include/clang/AST/ASTImporter.h
+++ b/clang/include/clang/AST/ASTImporter.h
@@ -256,14 +256,11 @@ class TypeSourceInfo;
     /// Declaration (from, to) pairs that are known not to be equivalent
     /// (which we have already complained about).
     NonEquivalentDeclSet NonEquivalentDecls;
-    // When template function return type is auto and return type is declared as
-    // typename from template params, there could be cycles in function
-    // importing when function decaration is still the need for return type
-    // declaration import. We have code path for nested types inside function
-    // (see hasReturnTypeDeclaredInside): assuming return type as VoidTy and
-    // calculate it later under UsedDifferentProtoType boolean. This class is
-    // reuse of this approach and make logic lazy - detect cycle - calculate
-    // return type later on.
+    /// A FunctionDecl can have properties that have a reference to the
+    /// function itself and are imported before the function is created. This
+    /// can come for example from auto return type or when template parameters
+    /// are used in the return type or parameters. This member is used to detect
+    /// cyclic import of FunctionDecl objects to avoid infinite recursion.
     std::unique_ptr<FunctionReturnTypeDeclCycleDetector>
         FunctionReturnTypeCycleDetector;
 
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index d348bfd097eda..819c6bfc970e9 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -1289,34 +1289,34 @@ using namespace clang;
 
 class ASTImporter::FunctionReturnTypeDeclCycleDetector {
 public:
-  class ScopedReturnTypeImport {
+  class DeclCycleMapInserter {
   public:
     // Do not track cycles on D == nullptr.
-    ScopedReturnTypeImport(FunctionReturnTypeDeclCycleDetector &owner,
-                           const FunctionDecl *D)
+    DeclCycleMapInserter(FunctionReturnTypeDeclCycleDetector &owner,
+                         const FunctionDecl *D)
         : CycleDetector(owner), D(D) {
       if (D)
         CycleDetector.FunctionReturnTypeDeclCycles.insert(D);
     }
-    ~ScopedReturnTypeImport() {
+    ~DeclCycleMapInserter() {
       if (D)
         CycleDetector.FunctionReturnTypeDeclCycles.erase(D);
     }
-    ScopedReturnTypeImport(const ScopedReturnTypeImport &) = delete;
-    ScopedReturnTypeImport &operator=(const ScopedReturnTypeImport &) = delete;
+    DeclCycleMapInserter(const DeclCycleMapInserter &) = delete;
+    DeclCycleMapInserter &operator=(const DeclCycleMapInserter &) = delete;
 
   private:
     FunctionReturnTypeDeclCycleDetector &CycleDetector;
     const FunctionDecl *D;
   };
 
-  ScopedReturnTypeImport DetectImportCycles(const FunctionDecl *D) {
-    if (!IsCycle(D))
-      return ScopedReturnTypeImport(*this, D);
-    return ScopedReturnTypeImport(*this, nullptr);
+  DeclCycleMapInserter detectImportCycle(const FunctionDecl *D) {
+    if (!isCycle(D))
+      return DeclCycleMapInserter(*this, D);
+    return DeclCycleMapInserter(*this, nullptr);
   }
 
-  bool IsCycle(const FunctionDecl *D) const {
+  bool isCycle(const FunctionDecl *D) const {
     return FunctionReturnTypeDeclCycles.find(D) !=
            FunctionReturnTypeDeclCycles.end();
   }
@@ -4076,7 +4076,7 @@ ExpectedDecl ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) {
     // Reuse this approach for auto return types declared as typenames from
     // template pamams, tracked in FunctionReturnTypeCycleDetector.
     if (hasReturnTypeDeclaredInside(D) ||
-        Importer.FunctionReturnTypeCycleDetector->IsCycle(D)) {
+        Importer.FunctionReturnTypeCycleDetector->isCycle(D)) {
       FromReturnTy = Importer.getFromContext().VoidTy;
       UsedDifferentProtoType = true;
     }
@@ -4100,7 +4100,7 @@ ExpectedDecl ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) {
 
   Error Err = Error::success();
   auto ScopedReturnTypeDeclCycleDetector =
-      Importer.FunctionReturnTypeCycleDetector->DetectImportCycles(D);
+      Importer.FunctionReturnTypeCycleDetector->detectImportCycle(D);
   auto T = importChecked(Err, FromTy);
   auto TInfo = importChecked(Err, FromTSI);
   auto ToInnerLocStart = importChecked(Err, D->getInnerLocStart());
diff --git a/clang/unittests/AST/ASTImporterTest.cpp b/clang/unittests/AST/ASTImporterTest.cpp
index ab63ddeb5e94b..bbfa591d3efa1 100644
--- a/clang/unittests/AST/ASTImporterTest.cpp
+++ b/clang/unittests/AST/ASTImporterTest.cpp
@@ -3204,7 +3204,7 @@ TEST_P(ImportExpr, UnresolvedMemberExpr) {
                  compoundStmt(has(callExpr(has(unresolvedMemberExpr())))))))));
 }
 
-TEST_P(ImportExpr, CycleInAutoTemplateSpec) {
+TEST_P(ImportDecl, CycleInAutoTemplateSpec) {
   MatchVerifier<Decl> Verifier;
   const char *Code = R"(
   template <class _CharT>

>From d57089e25bcbe65c38c6774bb7a05085ab00d700 Mon Sep 17 00:00:00 2001
From: Konstantin Ganenko <ganenkokb at yandex-team.ru>
Date: Tue, 14 Oct 2025 12:57:44 +0300
Subject: [PATCH 6/8] Remove hasReturnTypeDeclaredInside

Now FunctionReturnTypeCycleDetector works out with nested
 typenames as well.
---
 clang/lib/AST/ASTImporter.cpp | 38 ++++-------------------------------
 1 file changed, 4 insertions(+), 34 deletions(-)

diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index 819c6bfc970e9..c1e0781f0c023 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -744,13 +744,8 @@ namespace clang {
     Error ImportOverriddenMethods(CXXMethodDecl *ToMethod,
                                   CXXMethodDecl *FromMethod);
 
-    Expected<FunctionDecl *> FindFunctionTemplateSpecialization(
-        FunctionDecl *FromFD);
-
-    // Returns true if the given function has a placeholder return type and
-    // that type is declared inside the body of the function.
-    // E.g. auto f() { struct X{}; return X(); }
-    bool hasReturnTypeDeclaredInside(FunctionDecl *D);
+    Expected<FunctionDecl *>
+    FindFunctionTemplateSpecialization(FunctionDecl *FromFD);
   };
 
 template <typename InContainerTy>
@@ -3902,30 +3897,6 @@ class IsTypeDeclaredInsideVisitor
 };
 } // namespace
 
-/// This function checks if the given function has a return type that contains
-/// a reference (in any way) to a declaration inside the same function.
-bool ASTNodeImporter::hasReturnTypeDeclaredInside(FunctionDecl *D) {
-  QualType FromTy = D->getType();
-  const auto *FromFPT = FromTy->getAs<FunctionProtoType>();
-  assert(FromFPT && "Must be called on FunctionProtoType");
-
-  auto IsCXX11Lambda = [&]() {
-    if (Importer.FromContext.getLangOpts().CPlusPlus14) // C++14 or later
-      return false;
-
-    return isLambdaMethod(D);
-  };
-
-  QualType RetT = FromFPT->getReturnType();
-  if (isa<AutoType>(RetT.getTypePtr()) || IsCXX11Lambda()) {
-    FunctionDecl *Def = D->getDefinition();
-    IsTypeDeclaredInsideVisitor Visitor(Def ? Def : D);
-    return Visitor.CheckType(RetT);
-  }
-
-  return false;
-}
-
 ExplicitSpecifier
 ASTNodeImporter::importExplicitSpecifier(Error &Err, ExplicitSpecifier ESpec) {
   Expr *ExplicitExpr = ESpec.getExpr();
@@ -4074,9 +4045,8 @@ ExpectedDecl ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) {
     // To avoid an infinite recursion when importing, create the FunctionDecl
     // with a simplified return type.
     // Reuse this approach for auto return types declared as typenames from
-    // template pamams, tracked in FunctionReturnTypeCycleDetector.
-    if (hasReturnTypeDeclaredInside(D) ||
-        Importer.FunctionReturnTypeCycleDetector->isCycle(D)) {
+    // template params, tracked in FunctionReturnTypeCycleDetector.
+    if (Importer.FunctionReturnTypeCycleDetector->isCycle(D)) {
       FromReturnTy = Importer.getFromContext().VoidTy;
       UsedDifferentProtoType = true;
     }

>From 959ff8861faa9ba57e5f19adaa61932ff1eaa564 Mon Sep 17 00:00:00 2001
From: Konstantin Ganenko <ganenkokb at yandex-team.ru>
Date: Tue, 14 Oct 2025 17:45:11 +0300
Subject: [PATCH 7/8] Revert "Remove hasReturnTypeDeclaredInside"

This reverts commit d57089e25bcbe65c38c6774bb7a05085ab00d700.
---
 clang/lib/AST/ASTImporter.cpp | 38 +++++++++++++++++++++++++++++++----
 1 file changed, 34 insertions(+), 4 deletions(-)

diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index c1e0781f0c023..819c6bfc970e9 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -744,8 +744,13 @@ namespace clang {
     Error ImportOverriddenMethods(CXXMethodDecl *ToMethod,
                                   CXXMethodDecl *FromMethod);
 
-    Expected<FunctionDecl *>
-    FindFunctionTemplateSpecialization(FunctionDecl *FromFD);
+    Expected<FunctionDecl *> FindFunctionTemplateSpecialization(
+        FunctionDecl *FromFD);
+
+    // Returns true if the given function has a placeholder return type and
+    // that type is declared inside the body of the function.
+    // E.g. auto f() { struct X{}; return X(); }
+    bool hasReturnTypeDeclaredInside(FunctionDecl *D);
   };
 
 template <typename InContainerTy>
@@ -3897,6 +3902,30 @@ class IsTypeDeclaredInsideVisitor
 };
 } // namespace
 
+/// This function checks if the given function has a return type that contains
+/// a reference (in any way) to a declaration inside the same function.
+bool ASTNodeImporter::hasReturnTypeDeclaredInside(FunctionDecl *D) {
+  QualType FromTy = D->getType();
+  const auto *FromFPT = FromTy->getAs<FunctionProtoType>();
+  assert(FromFPT && "Must be called on FunctionProtoType");
+
+  auto IsCXX11Lambda = [&]() {
+    if (Importer.FromContext.getLangOpts().CPlusPlus14) // C++14 or later
+      return false;
+
+    return isLambdaMethod(D);
+  };
+
+  QualType RetT = FromFPT->getReturnType();
+  if (isa<AutoType>(RetT.getTypePtr()) || IsCXX11Lambda()) {
+    FunctionDecl *Def = D->getDefinition();
+    IsTypeDeclaredInsideVisitor Visitor(Def ? Def : D);
+    return Visitor.CheckType(RetT);
+  }
+
+  return false;
+}
+
 ExplicitSpecifier
 ASTNodeImporter::importExplicitSpecifier(Error &Err, ExplicitSpecifier ESpec) {
   Expr *ExplicitExpr = ESpec.getExpr();
@@ -4045,8 +4074,9 @@ ExpectedDecl ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) {
     // To avoid an infinite recursion when importing, create the FunctionDecl
     // with a simplified return type.
     // Reuse this approach for auto return types declared as typenames from
-    // template params, tracked in FunctionReturnTypeCycleDetector.
-    if (Importer.FunctionReturnTypeCycleDetector->isCycle(D)) {
+    // template pamams, tracked in FunctionReturnTypeCycleDetector.
+    if (hasReturnTypeDeclaredInside(D) ||
+        Importer.FunctionReturnTypeCycleDetector->isCycle(D)) {
       FromReturnTy = Importer.getFromContext().VoidTy;
       UsedDifferentProtoType = true;
     }

>From 786def129f10cc9d3e0c06bc1314bc368973030b Mon Sep 17 00:00:00 2001
From: Konstantin Ganenko <ganenkokb at yandex-team.ru>
Date: Tue, 14 Oct 2025 17:47:49 +0300
Subject: [PATCH 8/8] Fix misspelling

---
 clang/lib/AST/ASTImporter.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index 819c6bfc970e9..a3d13037d530a 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -4074,7 +4074,7 @@ ExpectedDecl ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) {
     // To avoid an infinite recursion when importing, create the FunctionDecl
     // with a simplified return type.
     // Reuse this approach for auto return types declared as typenames from
-    // template pamams, tracked in FunctionReturnTypeCycleDetector.
+    // template params, tracked in FunctionReturnTypeCycleDetector.
     if (hasReturnTypeDeclaredInside(D) ||
         Importer.FunctionReturnTypeCycleDetector->isCycle(D)) {
       FromReturnTy = Importer.getFromContext().VoidTy;



More information about the cfe-commits mailing list