diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp --- a/clang/lib/AST/ASTImporter.cpp +++ b/clang/lib/AST/ASTImporter.cpp @@ -45,6 +45,7 @@ #include "clang/AST/TypeVisitor.h" #include "clang/AST/UnresolvedSet.h" #include "clang/Basic/Builtins.h" +#include "clang/Basic/DiagnosticSema.h" #include "clang/Basic/ExceptionSpecificationType.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/IdentifierTable.h" @@ -646,6 +647,11 @@ 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 hasAutoReturnTypeDeclaredInside(FunctionDecl *D); }; template @@ -1545,6 +1551,10 @@ DeclarationName &Name, NamedDecl *&ToD, SourceLocation &Loc) { // Check if RecordDecl is in FunctionDecl parameters to avoid infinite loop. // example: int struct_in_proto(struct data_t{int a;int b;} *d); + // FIXME: We could support these constructs by importing a different type of + // this parameter and by importing the original type of the parameter only + // after the FunctionDecl is created. See + // VisitFunctionDecl::UsedDifferentProtoType. DeclContext *OrigDC = D->getDeclContext(); FunctionDecl *FunDecl; if (isa(D) && (FunDecl = dyn_cast(OrigDC)) && @@ -2993,6 +3003,46 @@ return Error::success(); } +// Returns true if the given D has a DeclContext up to the TranslationUnitDecl +// which is equal to the given DC. +bool isAncestorDeclContextOf(const DeclContext *DC, const Decl *D) { + const DeclContext *DCi = D->getDeclContext(); + while (DCi != D->getTranslationUnitDecl()) { + if (DCi == DC) + return true; + DCi = DCi->getParent(); + } + return false; +} + +bool ASTNodeImporter::hasAutoReturnTypeDeclaredInside(FunctionDecl *D) { + QualType FromTy = D->getType(); + const FunctionProtoType *FromFPT = FromTy->getAs(); + assert(FromFPT && "Must be called on FunctionProtoType"); + if (AutoType *AutoT = FromFPT->getReturnType()->getContainedAutoType()) { + QualType DeducedT = AutoT->getDeducedType(); + if (const RecordType *RecordT = + DeducedT.isNull() ? nullptr : dyn_cast(DeducedT)) { + RecordDecl *RD = RecordT->getDecl(); + assert(RD); + if (isAncestorDeclContextOf(D, RD)) { + assert(RD->getLexicalDeclContext() == RD->getDeclContext()); + return true; + } + } + } + if (const TypedefType *TypedefT = + dyn_cast(FromFPT->getReturnType())) { + TypedefNameDecl *TD = TypedefT->getDecl(); + assert(TD); + if (isAncestorDeclContextOf(D, TD)) { + assert(TD->getLexicalDeclContext() == TD->getDeclContext()); + return true; + } + } + return false; +} + ExpectedDecl ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) { SmallVector Redecls = getCanonicalForwardRedeclChain(D); @@ -3116,22 +3166,37 @@ return std::move(Err); QualType FromTy = D->getType(); - bool usedDifferentExceptionSpec = false; - - if (const auto *FromFPT = D->getType()->getAs()) { + // Set to true if we do not import the type of the function as is. There are + // cases when the original type would result in an infinite recursion during + // the import. To avoid an infinite recursion when importing, we create the + // FunctionDecl with a simplified function type and update it only after the + // relevant AST nodes are already imported. + bool UsedDifferentProtoType = false; + if (const auto *FromFPT = FromTy->getAs()) { + QualType FromReturnTy = FromFPT->getReturnType(); + // Functions with auto return type may define a struct inside their body + // and the return type could refer to that struct. + // E.g.: auto foo() { struct X{}; return X(); } + // To avoid an infinite recursion when importing, create the FunctionDecl + // with a simplified return type. + if (hasAutoReturnTypeDeclaredInside(D)) { + FromReturnTy = Importer.getFromContext().VoidTy; + UsedDifferentProtoType = true; + } FunctionProtoType::ExtProtoInfo FromEPI = FromFPT->getExtProtoInfo(); // FunctionProtoType::ExtProtoInfo's ExceptionSpecDecl can point to the // FunctionDecl that we are importing the FunctionProtoType for. // To avoid an infinite recursion when importing, create the FunctionDecl - // with a simplified function type and update it afterwards. + // with a simplified function type. if (FromEPI.ExceptionSpec.SourceDecl || FromEPI.ExceptionSpec.SourceTemplate || FromEPI.ExceptionSpec.NoexceptExpr) { FunctionProtoType::ExtProtoInfo DefaultEPI; - FromTy = Importer.getFromContext().getFunctionType( - FromFPT->getReturnType(), FromFPT->getParamTypes(), DefaultEPI); - usedDifferentExceptionSpec = true; + FromEPI = DefaultEPI; + UsedDifferentProtoType = true; } + FromTy = Importer.getFromContext().getFunctionType( + FromReturnTy, FromFPT->getParamTypes(), FromEPI); } QualType T; @@ -3265,14 +3330,6 @@ } } - if (usedDifferentExceptionSpec) { - // Update FunctionProtoType::ExtProtoInfo. - if (ExpectedType TyOrErr = import(D->getType())) - ToFunction->setType(*TyOrErr); - else - return TyOrErr.takeError(); - } - // Import the describing template function, if any. if (FromFT) { auto ToFTOrErr = import(FromFT); @@ -3304,6 +3361,14 @@ return std::move(Err); } + // Import and set the original type in case we used another type. + if (UsedDifferentProtoType) { + if (ExpectedType TyOrErr = import(D->getType())) + ToFunction->setType(*TyOrErr); + else + return TyOrErr.takeError(); + } + // FIXME: Other bits to merge? // If it is a template, import all related things. diff --git a/clang/unittests/AST/ASTImporterTest.cpp b/clang/unittests/AST/ASTImporterTest.cpp --- a/clang/unittests/AST/ASTImporterTest.cpp +++ b/clang/unittests/AST/ASTImporterTest.cpp @@ -10,9 +10,11 @@ // //===----------------------------------------------------------------------===// +#include "clang/ASTMatchers/ASTMatchers.h" #include "llvm/ADT/StringMap.h" #include "clang/AST/DeclContextInternals.h" +#include "gtest/gtest.h" #include "ASTImporterFixtures.h" #include "MatchVerifier.h" @@ -5599,6 +5601,113 @@ 2u); } +struct ImportAutoFunctions : ASTImporterOptionSpecificTestBase {}; + +TEST_P(ImportAutoFunctions, ReturnWithTypedefDeclaredInside) { + Decl *FromTU = getTuDecl( + R"( + auto X = [](long l) { + using int_type = long; + auto dur = 13; + return static_cast(dur); + }; + )", + Lang_CXX14, "input0.cc"); + CXXMethodDecl *From = + FirstDeclMatcher().match(FromTU, cxxMethodDecl()); + + // Explicitly set the return type of the lambda's operator() to the TypeAlias. + // Normally the return type would be the built-in 'long' type. However, there + // are cases when Clang does not use the canonical type and the TypeAlias is + // used. I could not create such an AST from regular source code, it requires + // some special state in the preprocessor. I've found such an AST when Clang + // parsed libcxx/src/filesystem/directory_iterator.cpp, but could not reduce + // that with creduce, because after preprocessing, the AST no longer + // contained the TypeAlias as a return type of the lambda. + ASTContext &Ctx = From->getASTContext(); + TypeAliasDecl *FromTA = + FirstDeclMatcher().match(FromTU, typeAliasDecl()); + QualType TT = Ctx.getTypedefType(FromTA); + const FunctionProtoType *FPT = cast(From->getType()); + QualType NewFunType = + Ctx.getFunctionType(TT, FPT->getParamTypes(), FPT->getExtProtoInfo()); + From->setType(NewFunType); + + CXXMethodDecl *To = Import(From, Lang_CXX14); + EXPECT_TRUE(To); + EXPECT_TRUE(isa(To->getReturnType())); +} + +TEST_P(ImportAutoFunctions, ReturnWithStructDeclaredInside) { + Decl *FromTU = getTuDecl( + R"( + auto foo() { + struct X {}; + return X(); + } + )", + Lang_CXX14, "input0.cc"); + FunctionDecl *From = + FirstDeclMatcher().match(FromTU, functionDecl()); + + FunctionDecl *To = Import(From, Lang_CXX14); + EXPECT_TRUE(To); + EXPECT_TRUE(isa(To->getReturnType())); +} + +TEST_P(ImportAutoFunctions, ReturnWithStructDeclaredInside2) { + Decl *FromTU = getTuDecl( + R"( + auto foo() { + struct X {}; + return X(); + } + )", + Lang_CXX14, "input0.cc"); + FunctionDecl *From = + FirstDeclMatcher().match(FromTU, functionDecl()); + + // This time import the type directly. + QualType ToT = ImportType(From->getType(), From, Lang_CXX14); + const FunctionProtoType *FPT = cast(ToT); + EXPECT_TRUE(isa(FPT->getReturnType())); +} + +TEST_P(ImportAutoFunctions, ReturnWithTypedefToStructDeclaredInside) { + Decl *FromTU = getTuDecl( + R"( + auto foo() { + struct X {}; + using Y = X; + return Y(); + } + )", + Lang_CXX14, "input0.cc"); + FunctionDecl *From = + FirstDeclMatcher().match(FromTU, functionDecl()); + + FunctionDecl *To = Import(From, Lang_CXX14); + EXPECT_TRUE(To); + EXPECT_TRUE(isa(To->getReturnType())); +} + +TEST_P(ImportAutoFunctions, ReturnWithStructDeclaredNestedInside) { + Decl *FromTU = getTuDecl( + R"( + auto foo() { + struct X { struct Y{}; }; + return X::Y(); + } + )", + Lang_CXX14, "input0.cc"); + FunctionDecl *From = + FirstDeclMatcher().match(FromTU, functionDecl()); + + FunctionDecl *To = Import(From, Lang_CXX14); + EXPECT_TRUE(To); + EXPECT_TRUE(isa(To->getReturnType())); +} + INSTANTIATE_TEST_CASE_P(ParameterizedTests, ASTImporterLookupTableTest, DefaultTestValuesForRunOptions, ); @@ -5626,6 +5735,9 @@ INSTANTIATE_TEST_CASE_P(ParameterizedTests, ImportFunctions, DefaultTestValuesForRunOptions, ); +INSTANTIATE_TEST_CASE_P(ParameterizedTests, ImportAutoFunctions, + DefaultTestValuesForRunOptions, ); + INSTANTIATE_TEST_CASE_P(ParameterizedTests, ImportFunctionTemplates, DefaultTestValuesForRunOptions, );