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

On importing template specialization with auto return type cycle occurs when 
return type is not nested one, but typename from template arguments and other 
template.
There is code, that prevents cycle to auto return types when nested type 
declared. Solved case differs somehow from nested types, but have same solution 
with UsedDifferentProtoType - with delayed return type determining.

>From b76ab82d3ec878653d177502a2aae1f46fa103b7 Mon Sep 17 00:00:00 2001
From: Konstantin Ganenko <[email protected]>
Date: Tue, 7 Oct 2025 14:36:49 +0300
Subject: [PATCH] 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"(

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

Reply via email to