From 63e609960bc477fa516dac9b707a767e3719cbb3 Mon Sep 17 00:00:00 2001 From: Konstantin Ganenko 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 DeclTypeCycles; using FoundDeclsTy = SmallVector; 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 Verifier; + const char *Code = R"( + template + struct basic_string { + using value_type = _CharT; + }; + + template + struct basic_string_view { + using value_type = T; + }; + + using string_view = basic_string_view; + using string = basic_string; + + template + struct span { + }; + + template + auto StrCatT(span pieces) { + basic_string result; + return result; + } + + string StrCat(span pieces) { + return StrCatT(pieces); + } + + string StrCat(span pieces) { + return StrCatT(pieces); + } + + template + auto declToImport(T pieces) { + return StrCat(pieces); + } + + void test() { + span 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 Verifier; const char *Code = R"( From 3e2323025a1e64265adf587c2191d244506640dd Mon Sep 17 00:00:00 2001 From: Konstantin Ganenko 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 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 Aux; }; + class FunctionReturnTypeDeclCycleDetector; + private: std::shared_ptr 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 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 + FunctionReturnTypeCycleDetector; using FoundDeclsTy = SmallVector; 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 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 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()) { // Create a default state without the lookup table: LLDB case. if (!SharedState) { From 087f9828ee9ec27cf8640acc74af49929fe10b05 Mon Sep 17 00:00:00 2001 From: Konstantin Ganenko 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 FunctionReturnTypeDeclCycles; + llvm::DenseSet FunctionReturnTypeDeclCycles; }; ExpectedType ASTNodeImporter::VisitType(const Type *T) { From 6d9a3a1d60a3e421e136422d9c3e12e52d92ac45 Mon Sep 17 00:00:00 2001 From: Konstantin Ganenko 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 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 Verifier; const char *Code = R"( template From d57089e25bcbe65c38c6774bb7a05085ab00d700 Mon Sep 17 00:00:00 2001 From: Konstantin Ganenko 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 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 + FindFunctionTemplateSpecialization(FunctionDecl *FromFD); }; template @@ -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(); - 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(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 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 - FindFunctionTemplateSpecialization(FunctionDecl *FromFD); + Expected 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 @@ -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(); + 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(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 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;