[ASTImporter] Support functions with placeholder return types ...

Summary:
Support functions with placeholder return types even in cases when the type is
declared in the body of the function.
Example: auto f() { struct X{}; return X(); }

Reviewers: balazske, a_sidorin, a.sidorin, shafik

Subscribers: rnkovacs, dkrupp, Szelethus, gamesh411, teemperor, cfe-commits

Tags: #clang

Differential Revision: https://reviews.llvm.org/D70819
This commit is contained in:
Gabor Marton 2019-12-12 17:13:35 +01:00
parent 11b2b2f4b1
commit 25234fd69e
5 changed files with 274 additions and 15 deletions

View File

@ -648,6 +648,11 @@ namespace clang {
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 hasAutoReturnTypeDeclaredInside(FunctionDecl *D);
};
template <typename InContainerTy>
@ -1547,6 +1552,10 @@ Error ASTNodeImporter::ImportDeclParts(
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<RecordDecl>(D) && (FunDecl = dyn_cast<FunctionDecl>(OrigDC)) &&
@ -3005,6 +3014,46 @@ Error ASTNodeImporter::ImportFunctionDeclBody(FunctionDecl *FromFD,
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<FunctionProtoType>();
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<RecordType>(DeducedT)) {
RecordDecl *RD = RecordT->getDecl();
assert(RD);
if (isAncestorDeclContextOf(D, RD)) {
assert(RD->getLexicalDeclContext() == RD->getDeclContext());
return true;
}
}
}
if (const TypedefType *TypedefT =
dyn_cast<TypedefType>(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<Decl *, 2> Redecls = getCanonicalForwardRedeclChain(D);
@ -3128,22 +3177,37 @@ ExpectedDecl ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) {
return std::move(Err);
QualType FromTy = D->getType();
bool usedDifferentExceptionSpec = false;
if (const auto *FromFPT = D->getType()->getAs<FunctionProtoType>()) {
// 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<FunctionProtoType>()) {
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;
@ -3277,14 +3341,6 @@ ExpectedDecl ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) {
}
}
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);
@ -3316,6 +3372,14 @@ ExpectedDecl ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) {
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.

View File

@ -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"
@ -5623,6 +5625,188 @@ TEST_P(ASTImporterOptionSpecificTestBase, ImplicitlyDeclareSelf) {
EXPECT_TRUE(ToMethod->getSelfDecl() != nullptr);
}
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<int_type>(dur);
};
)",
Lang_CXX14, "input0.cc");
CXXMethodDecl *From =
FirstDeclMatcher<CXXMethodDecl>().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<TypeAliasDecl>().match(FromTU, typeAliasDecl());
QualType TT = Ctx.getTypedefType(FromTA);
const FunctionProtoType *FPT = cast<FunctionProtoType>(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<TypedefType>(To->getReturnType()));
}
TEST_P(ImportAutoFunctions, ReturnWithStructDeclaredInside) {
Decl *FromTU = getTuDecl(
R"(
auto foo() {
struct X {};
return X();
}
)",
Lang_CXX14, "input0.cc");
FunctionDecl *From =
FirstDeclMatcher<FunctionDecl>().match(FromTU, functionDecl());
FunctionDecl *To = Import(From, Lang_CXX14);
EXPECT_TRUE(To);
EXPECT_TRUE(isa<AutoType>(To->getReturnType()));
}
TEST_P(ImportAutoFunctions, ReturnWithStructDeclaredInside2) {
Decl *FromTU = getTuDecl(
R"(
auto foo() {
struct X {};
return X();
}
)",
Lang_CXX14, "input0.cc");
FunctionDecl *From =
FirstDeclMatcher<FunctionDecl>().match(FromTU, functionDecl());
// This time import the type directly.
QualType ToT = ImportType(From->getType(), From, Lang_CXX14);
const FunctionProtoType *FPT = cast<FunctionProtoType>(ToT);
EXPECT_TRUE(isa<AutoType>(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<FunctionDecl>().match(FromTU, functionDecl());
FunctionDecl *To = Import(From, Lang_CXX14);
EXPECT_TRUE(To);
EXPECT_TRUE(isa<AutoType>(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<FunctionDecl>().match(FromTU, functionDecl());
FunctionDecl *To = Import(From, Lang_CXX14);
EXPECT_TRUE(To);
EXPECT_TRUE(isa<AutoType>(To->getReturnType()));
}
TEST_P(ImportAutoFunctions, ReturnWithInternalLambdaType) {
Decl *FromTU = getTuDecl(
R"(
auto f() {
auto l = []() {
struct X {};
return X();
};
return l();
}
)",
Lang_CXX17, "input0.cc");
FunctionDecl *From = FirstDeclMatcher<FunctionDecl>().match(
FromTU, functionDecl(hasName("f")));
FunctionDecl *To = Import(From, Lang_CXX17);
EXPECT_TRUE(To);
EXPECT_TRUE(isa<AutoType>(To->getReturnType()));
}
TEST_P(ImportAutoFunctions, ReturnWithTypeInIf) {
Decl *FromTU = getTuDecl(
R"(
auto f() {
if (struct X {} x; true)
return X();
else
return X();
}
)",
Lang_CXX17, "input0.cc");
FunctionDecl *From = FirstDeclMatcher<FunctionDecl>().match(
FromTU, functionDecl(hasName("f")));
FunctionDecl *To = Import(From, Lang_CXX17);
EXPECT_TRUE(To);
EXPECT_TRUE(isa<AutoType>(To->getReturnType()));
}
TEST_P(ImportAutoFunctions, ReturnWithTypeInFor) {
Decl *FromTU = getTuDecl(
R"(
auto f() {
for (struct X {} x;;)
return X();
}
)",
Lang_CXX17, "input0.cc");
FunctionDecl *From = FirstDeclMatcher<FunctionDecl>().match(
FromTU, functionDecl(hasName("f")));
FunctionDecl *To = Import(From, Lang_CXX17);
EXPECT_TRUE(To);
EXPECT_TRUE(isa<AutoType>(To->getReturnType()));
}
TEST_P(ImportAutoFunctions, ReturnWithTypeInSwitch) {
Decl *FromTU = getTuDecl(
R"(
auto f() {
switch (struct X {} x; 10) {
case 10:
return X();
}
}
)",
Lang_CXX17, "input0.cc");
FunctionDecl *From = FirstDeclMatcher<FunctionDecl>().match(
FromTU, functionDecl(hasName("f")));
FunctionDecl *To = Import(From, Lang_CXX17);
EXPECT_TRUE(To);
EXPECT_TRUE(isa<AutoType>(To->getReturnType()));
}
INSTANTIATE_TEST_CASE_P(ParameterizedTests, ASTImporterLookupTableTest,
DefaultTestValuesForRunOptions, );
@ -5650,6 +5834,9 @@ INSTANTIATE_TEST_CASE_P(ParameterizedTests, RedirectingImporterTest,
INSTANTIATE_TEST_CASE_P(ParameterizedTests, ImportFunctions,
DefaultTestValuesForRunOptions, );
INSTANTIATE_TEST_CASE_P(ParameterizedTests, ImportAutoFunctions,
DefaultTestValuesForRunOptions, );
INSTANTIATE_TEST_CASE_P(ParameterizedTests, ImportFunctionTemplates,
DefaultTestValuesForRunOptions, );

View File

@ -34,6 +34,9 @@ ArgVector getBasicRunOptionsForLanguage(Language Lang) {
case Lang_CXX14:
BasicArgs = {"-std=c++14", "-frtti"};
break;
case Lang_CXX17:
BasicArgs = {"-std=c++17", "-frtti"};
break;
case Lang_CXX2a:
BasicArgs = {"-std=c++2a", "-frtti"};
break;

View File

@ -28,6 +28,7 @@ enum Language {
Lang_CXX,
Lang_CXX11,
Lang_CXX14,
Lang_CXX17,
Lang_CXX2a,
Lang_OpenCL,
Lang_OBJCXX

View File

@ -108,6 +108,10 @@ testing::AssertionResult MatchVerifier<NodeType>::match(
Args.push_back("-std=c++14");
FileName = "input.cc";
break;
case Lang_CXX17:
Args.push_back("-std=c++17");
FileName = "input.cc";
break;
case Lang_CXX2a:
Args.push_back("-std=c++2a");
FileName = "input.cc";