Index: unittests/AST/ASTImporterTest.cpp =================================================================== --- unittests/AST/ASTImporterTest.cpp +++ unittests/AST/ASTImporterTest.cpp @@ -17,6 +17,8 @@ #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Tooling/Tooling.h" + +#include "DeclMatcher.h" #include "gtest/gtest.h" namespace clang { @@ -29,7 +31,7 @@ return Lang == Lang_CXX || Lang == Lang_CXX11; } -static RunOptions getRunOptionsForLanguage(Language Lang) { +static ArgVector getBasicRunOptionsForLanguage(Language Lang) { ArgVector BasicArgs; // Test with basic arguments. switch (Lang) { @@ -49,6 +51,11 @@ case Lang_OBJCXX: llvm_unreachable("Not implemented yet!"); } + return BasicArgs; +} + +static RunOptions getRunOptionsForLanguage(Language Lang) { + ArgVector BasicArgs = getBasicRunOptionsForLanguage(Lang); // For C++, test with "-fdelayed-template-parsing" enabled to handle MSVC // default behaviour. @@ -61,6 +68,19 @@ return {BasicArgs}; } +// Creates a virtual file and assigns that to the context of given AST. If the +// file already exists then the file will not be created again as a duplicate. +static void createVirtualFileIfNeeded(ASTUnit *ToAST, StringRef FileName, + const std::string &Code) { + assert(ToAST); + ASTContext &ToCtx = ToAST->getASTContext(); + auto *OFS = static_cast( + ToCtx.getSourceManager().getFileManager().getVirtualFileSystem().get()); + auto *MFS = + static_cast(OFS->overlays_begin()->get()); + MFS->addFile(FileName, 0, llvm::MemoryBuffer::getMemBuffer(Code.c_str())); +} + template testing::AssertionResult testImport(const std::string &FromCode, const ArgVector &FromArgs, @@ -79,11 +99,7 @@ // Add input.cc to virtual file system so importer can 'find' it // while importing SourceLocations. - vfs::OverlayFileSystem *OFS = static_cast( - ToCtx.getSourceManager().getFileManager().getVirtualFileSystem().get()); - vfs::InMemoryFileSystem *MFS = static_cast( - OFS->overlays_begin()->get()); - MFS->addFile(InputFileName, 0, llvm::MemoryBuffer::getMemBuffer(FromCode)); + createVirtualFileIfNeeded(ToAST.get(), InputFileName, FromCode); ASTImporter Importer(ToCtx, ToAST->getFileManager(), FromCtx, FromAST->getFileManager(), false); @@ -133,6 +149,165 @@ Verifier, AMatcher)); } +const StringRef DeclToImportID = "declToImport"; + +// This class provides generic methods to write tests which can check internal +// attributes of AST nodes like getPreviousDecl(), isVirtual(), etc. Also, +// this fixture makes it possible to import from several "From" contexts. +class ASTImporterTestBase : public ::testing::TestWithParam { + + const char *const InputFileName = "input.cc"; + const char *const OutputFileName = "output.cc"; + + // Buffer for the To context, must live in the test scope. + std::string ToCode; + + struct TU { + // Buffer for the context, must live in the test scope. + std::string Code; + std::string FileName; + std::unique_ptr Unit; + TranslationUnitDecl *TUDecl = nullptr; + TU(StringRef Code, StringRef FileName, ArgVector Args) + : Code(Code), FileName(FileName), + Unit(tooling::buildASTFromCodeWithArgs(this->Code, Args, + this->FileName)), + TUDecl(Unit->getASTContext().getTranslationUnitDecl()) {} + }; + + // We may have several From contexts and related translation units. In each + // AST, the buffers for the source are handled via references and are set + // during the creation of the AST. These references must point to a valid + // buffer until the AST is alive. Thus, we must use a list in order to avoid + // moving of the stored objects because that would mean breaking the + // references in the AST. By using a vector a move could happen when the + // vector is expanding, with the list we won't have these issues. + std::list FromTUs; + +public: + // We may have several From context but only one To context. + std::unique_ptr ToAST; + + // Returns the argument vector used for a specific language, this set + // can be tweaked by the test parameters. + ArgVector getArgVectorForLanguage(Language Lang) { + ArgVector Args = getBasicRunOptionsForLanguage(Lang); + ArgVector ExtraArgs = GetParam(); + for (const auto& Arg : ExtraArgs) { + Args.push_back(Arg); + } + return Args; + } + + // Creates an AST both for the From and To source code and imports the Decl + // of the identifier into the To context. + // Must not be called more than once within the same test. + std::tuple + getImportedDecl(StringRef FromSrcCode, Language FromLang, StringRef ToSrcCode, + Language ToLang, StringRef Identifier = DeclToImportID) { + ArgVector FromArgs = getArgVectorForLanguage(FromLang), + ToArgs = getArgVectorForLanguage(ToLang); + + FromTUs.emplace_back(FromSrcCode, InputFileName, FromArgs); + TU &FromTU = FromTUs.back(); + + ToCode = ToSrcCode; + assert(!ToAST); + ToAST = tooling::buildASTFromCodeWithArgs(ToCode, ToArgs, OutputFileName); + + ASTContext &FromCtx = FromTU.Unit->getASTContext(), + &ToCtx = ToAST->getASTContext(); + + createVirtualFileIfNeeded(ToAST.get(), InputFileName, FromTU.Code); + + ASTImporter Importer(ToCtx, ToAST->getFileManager(), FromCtx, + FromTU.Unit->getFileManager(), false); + + IdentifierInfo *ImportedII = &FromCtx.Idents.get(Identifier); + assert(ImportedII && "Declaration with the given identifier " + "should be specified in test!"); + DeclarationName ImportDeclName(ImportedII); + SmallVector FoundDecls; + FromCtx.getTranslationUnitDecl()->localUncachedLookup(ImportDeclName, + FoundDecls); + + assert(FoundDecls.size() == 1); + + Decl *Imported = Importer.Import(FoundDecls.front()); + assert(Imported); + return std::make_tuple(*FoundDecls.begin(), Imported); + } + + // Creates a TU decl for the given source code. + // May be called several times in a given test. + TranslationUnitDecl *getTuDecl(StringRef SrcCode, Language Lang, + StringRef FileName = "input.cc") { + assert( + std::find_if(FromTUs.begin(), FromTUs.end(), [FileName](const TU &E) { + return E.FileName == FileName; + }) == FromTUs.end()); + + ArgVector Args = getArgVectorForLanguage(Lang); + FromTUs.emplace_back(SrcCode, FileName, Args); + TU &Tu = FromTUs.back(); + + return Tu.TUDecl; + } + + // Import the given Decl into the ToCtx. + // May be called several times in a given test. + // The different instances of the param From may have different ASTContext. + Decl *Import(Decl *From, Language ToLang) { + if (!ToAST) { + ArgVector ToArgs = getArgVectorForLanguage(ToLang); + // Build the AST from an empty file. + ToAST = + tooling::buildASTFromCodeWithArgs(/*Code=*/"", ToArgs, "empty.cc"); + } + + // Create a virtual file in the To Ctx which corresponds to the file from + // which we want to import the `From` Decl. Without this source locations + // will be invalid in the ToCtx. + auto It = std::find_if(FromTUs.begin(), FromTUs.end(), [From](const TU &E) { + return E.TUDecl == From->getTranslationUnitDecl(); + }); + assert(It != FromTUs.end()); + createVirtualFileIfNeeded(ToAST.get(), It->FileName, It->Code); + + ASTContext &FromCtx = From->getASTContext(), + &ToCtx = ToAST->getASTContext(); + ASTImporter Importer(ToCtx, ToAST->getFileManager(), FromCtx, + FromCtx.getSourceManager().getFileManager(), false); + return Importer.Import(From); + } + + ~ASTImporterTestBase() { + if (!::testing::Test::HasFailure()) return; + + for (auto &Tu : FromTUs) { + assert(Tu.Unit); + llvm::errs() << "FromAST:\n"; + Tu.Unit->getASTContext().getTranslationUnitDecl()->dump(); + llvm::errs() << "\n"; + } + if (ToAST) { + llvm::errs() << "ToAST:\n"; + ToAST->getASTContext().getTranslationUnitDecl()->dump(); + } + } +}; + +AST_MATCHER_P(RecordDecl, hasFieldOrder, std::vector, Order) { + size_t Index = 0; + for (FieldDecl *Field : Node.fields()) { + if (Index == Order.size()) + return false; + if (Field->getName() != Order[Index]) + return false; + ++Index; + } + return Index == Order.size(); +} TEST(ImportExpr, ImportStringLiteral) { MatchVerifier Verifier; @@ -816,5 +991,517 @@ has(fieldDecl(hasType(dependentSizedArrayType()))))))); } +TEST_P(ASTImporterTestBase, DISABLED_ImportFunctionWithBackReferringParameter) { + Decl *From, *To; + std::tie(From, To) = getImportedDecl( + R"( + template struct X {}; + + void declToImport(int y, X &x) {} + + template <> struct X { + void g() { + X x; + declToImport(0, x); + } + }; + )", + Lang_CXX, "", Lang_CXX); + + MatchVerifier Verifier; + auto Matcher = functionDecl(hasName("declToImport"), + parameterCountIs(2), + hasParameter(0, hasName("y")), + hasParameter(1, hasName("x")), + hasParameter(1, hasType(asString("X &")))); + ASSERT_TRUE(Verifier.match(From, Matcher)); + EXPECT_TRUE(Verifier.match(To, Matcher)); +} + +TEST_P(ASTImporterTestBase, + TUshouldNotContainTemplatedDeclOfFunctionTemplates) { + Decl *From, *To; + std::tie(From, To) = + getImportedDecl("template void declToImport() { T a = 1; }" + "void instantiate() { declToImport(); }", + Lang_CXX, "", Lang_CXX); + + auto Check = [](Decl *D) -> bool { + auto TU = D->getTranslationUnitDecl(); + for (auto Child : TU->decls()) { + if (auto *FD = dyn_cast(Child)) { + if (FD->getNameAsString() == "declToImport") { + GTEST_NONFATAL_FAILURE_( + "TU should not contain any FunctionDecl with name declToImport"); + return false; + } + } + } + return true; + }; + + ASSERT_TRUE(Check(From)); + EXPECT_TRUE(Check(To)); +} + +TEST_P(ASTImporterTestBase, TUshouldNotContainTemplatedDeclOfClassTemplates) { + Decl *From, *To; + std::tie(From, To) = + getImportedDecl("template struct declToImport { T t; };" + "void instantiate() { declToImport(); }", + Lang_CXX, "", Lang_CXX); + + auto Check = [](Decl *D) -> bool { + auto TU = D->getTranslationUnitDecl(); + for (auto Child : TU->decls()) { + if (auto *RD = dyn_cast(Child)) { + if (RD->getNameAsString() == "declToImport") { + GTEST_NONFATAL_FAILURE_( + "TU should not contain any CXXRecordDecl with name declToImport"); + return false; + } + } + } + return true; + }; + + ASSERT_TRUE(Check(From)); + EXPECT_TRUE(Check(To)); +} + +TEST_P(ASTImporterTestBase, TUshouldNotContainTemplatedDeclOfTypeAlias) { + Decl *From, *To; + std::tie(From, To) = + getImportedDecl( + "template struct X {};" + "template using declToImport = X;" + "void instantiate() { declToImport a; }", + Lang_CXX11, "", Lang_CXX11); + + auto Check = [](Decl *D) -> bool { + auto TU = D->getTranslationUnitDecl(); + for (auto Child : TU->decls()) { + if (auto *AD = dyn_cast(Child)) { + if (AD->getNameAsString() == "declToImport") { + GTEST_NONFATAL_FAILURE_( + "TU should not contain any TypeAliasDecl with name declToImport"); + return false; + } + } + } + return true; + }; + + ASSERT_TRUE(Check(From)); + EXPECT_TRUE(Check(To)); +} + +TEST_P( + ASTImporterTestBase, + DISABLED_TUshouldNotContainClassTemplateSpecializationOfImplicitInstantiation) { + + Decl *From, *To; + std::tie(From, To) = getImportedDecl( + R"( + template + class Base {}; + class declToImport : public Base {}; + )", + Lang_CXX, "", Lang_CXX); + + // Check that the ClassTemplateSpecializationDecl is NOT the child of the TU. + auto Pattern = + translationUnitDecl(unless(has(classTemplateSpecializationDecl()))); + ASSERT_TRUE( + MatchVerifier{}.match(From->getTranslationUnitDecl(), Pattern)); + EXPECT_TRUE( + MatchVerifier{}.match(To->getTranslationUnitDecl(), Pattern)); + + // Check that the ClassTemplateSpecializationDecl is the child of the + // ClassTemplateDecl. + Pattern = translationUnitDecl(has(classTemplateDecl( + hasName("Base"), has(classTemplateSpecializationDecl())))); + ASSERT_TRUE( + MatchVerifier{}.match(From->getTranslationUnitDecl(), Pattern)); + EXPECT_TRUE( + MatchVerifier{}.match(To->getTranslationUnitDecl(), Pattern)); +} + +TEST_P(ASTImporterTestBase, + TUshouldContainClassTemplateSpecializationOfExplicitInstantiation) { + Decl *From, *To; + std::tie(From, To) = getImportedDecl( + R"( + namespace NS { + template + class X {}; + template class X; + } + )", + Lang_CXX, "", Lang_CXX, "NS"); + + // Check that the ClassTemplateSpecializationDecl is NOT the child of the + // ClassTemplateDecl. + auto Pattern = namespaceDecl(has(classTemplateDecl( + hasName("X"), unless(has(classTemplateSpecializationDecl()))))); + ASSERT_TRUE(MatchVerifier{}.match(From, Pattern)); + EXPECT_TRUE(MatchVerifier{}.match(To, Pattern)); + + // Check that the ClassTemplateSpecializationDecl is the child of the + // NamespaceDecl. + Pattern = namespaceDecl(has(classTemplateSpecializationDecl(hasName("X")))); + ASSERT_TRUE(MatchVerifier{}.match(From, Pattern)); + EXPECT_TRUE(MatchVerifier{}.match(To, Pattern)); +} + +TEST_P(ASTImporterTestBase, CXXRecordDeclFieldsShouldBeInCorrectOrder) { + Decl *From, *To; + std::tie(From, To) = + getImportedDecl( + "struct declToImport { int a; int b; };", + Lang_CXX11, "", Lang_CXX11); + + MatchVerifier Verifier; + ASSERT_TRUE(Verifier.match(From, cxxRecordDecl(hasFieldOrder({"a", "b"})))); + EXPECT_TRUE(Verifier.match(To, cxxRecordDecl(hasFieldOrder({"a", "b"})))); +} + +TEST_P(ASTImporterTestBase, + DISABLED_CXXRecordDeclFieldOrderShouldNotDependOnImportOrder) { + Decl *From, *To; + std::tie(From, To) = getImportedDecl( + // The original recursive algorithm of ASTImporter first imports 'c' then + // 'b' and lastly 'a'. Therefore we must restore the order somehow. + R"s( + struct declToImport { + int a = c + b; + int b = 1; + int c = 2; + }; + )s", + Lang_CXX11, "", Lang_CXX11); + + MatchVerifier Verifier; + ASSERT_TRUE( + Verifier.match(From, cxxRecordDecl(hasFieldOrder({"a", "b", "c"})))); + EXPECT_TRUE( + Verifier.match(To, cxxRecordDecl(hasFieldOrder({"a", "b", "c"})))); +} + +TEST_P(ASTImporterTestBase, DISABLED_ShouldImportImplicitCXXRecordDecl) { + Decl *From, *To; + std::tie(From, To) = getImportedDecl( + R"( + template + struct declToImport { + }; + )", + Lang_CXX, "", Lang_CXX); + + MatchVerifier Verifier; + // Match the implicit Decl. + auto Matcher = classTemplateDecl(has(cxxRecordDecl(has(cxxRecordDecl())))); + ASSERT_TRUE(Verifier.match(From, Matcher)); + EXPECT_TRUE(Verifier.match(To, Matcher)); +} + +TEST_P( + ASTImporterTestBase, + DISABLED_ShouldImportImplicitCXXRecordDeclOfClassTemplateSpecializationDecl) { + Decl *From, *To; + std::tie(From, To) = getImportedDecl( + R"( + template + class Base {}; + class declToImport : public Base {}; + )", + Lang_CXX, "", Lang_CXX); + + auto hasImplicitClass = has(cxxRecordDecl()); + auto Pattern = translationUnitDecl(has(classTemplateDecl( + hasName("Base"), + has(classTemplateSpecializationDecl(hasImplicitClass))))); + ASSERT_TRUE( + MatchVerifier{}.match(From->getTranslationUnitDecl(), Pattern)); + EXPECT_TRUE( + MatchVerifier{}.match(To->getTranslationUnitDecl(), Pattern)); +} + +TEST_P(ASTImporterTestBase, IDNSOrdinary) { + Decl *From, *To; + std::tie(From, To) = + getImportedDecl("void declToImport() {}", Lang_CXX, "", Lang_CXX); + + MatchVerifier Verifier; + auto Matcher = functionDecl(); + ASSERT_TRUE(Verifier.match(From, Matcher)); + EXPECT_TRUE(Verifier.match(To, Matcher)); + EXPECT_EQ(From->getIdentifierNamespace(), To->getIdentifierNamespace()); +} + +TEST_P(ASTImporterTestBase, DISABLED_IDNSOfNonmemberOperator) { + Decl *FromTU = getTuDecl( + R"( + struct X {}; + void operator<<(int, X); + )", + Lang_CXX); + Decl *From = LastDeclMatcher{}.match(FromTU, functionDecl()); + const Decl *To = Import(From, Lang_CXX); + EXPECT_EQ(From->getIdentifierNamespace(), To->getIdentifierNamespace()); +} + +TEST_P(ASTImporterTestBase, + ShouldImportMembersOfClassTemplateSpecializationDecl) { + Decl *From, *To; + std::tie(From, To) = getImportedDecl( + R"( + template + class Base { int a; }; + class declToImport : Base {}; + )", + Lang_CXX, "", Lang_CXX); + + auto Pattern = translationUnitDecl(has(classTemplateDecl( + hasName("Base"), + has(classTemplateSpecializationDecl(has(fieldDecl(hasName("a")))))))); + ASSERT_TRUE( + MatchVerifier{}.match(From->getTranslationUnitDecl(), Pattern)); + EXPECT_TRUE( + MatchVerifier{}.match(To->getTranslationUnitDecl(), Pattern)); +} + +INSTANTIATE_TEST_CASE_P( + ParameterizedTests, ASTImporterTestBase, + ::testing::Values(ArgVector(), ArgVector{"-fdelayed-template-parsing"}),); + +struct ImportFunctions : ASTImporterTestBase {}; + +TEST_P(ImportFunctions, + PrototypeShouldBeImportedAsAPrototypeWhenThereIsNoDefinition) { + Decl *FromTU = getTuDecl("void f();", Lang_CXX); + auto Pattern = functionDecl(hasName("f")); + FunctionDecl *FromD = + FirstDeclMatcher().match(FromTU, Pattern); + + Decl *ImportedD = Import(FromD, Lang_CXX); + Decl *ToTU = ImportedD->getTranslationUnitDecl(); + + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), 1u); + EXPECT_TRUE(!cast(ImportedD)->doesThisDeclarationHaveABody()); +} + +TEST_P(ImportFunctions, + PrototypeShouldBeImportedAsDefintionWhenThereIsADefinition) { + Decl *FromTU = getTuDecl("void f(); void f() {}", Lang_CXX); + auto Pattern = functionDecl(hasName("f")); + FunctionDecl *FromD = // Prototype + FirstDeclMatcher().match(FromTU, Pattern); + + Decl *ImportedD = Import(FromD, Lang_CXX); + Decl *ToTU = ImportedD->getTranslationUnitDecl(); + + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), 1u); + EXPECT_TRUE(cast(ImportedD)->doesThisDeclarationHaveABody()); +} + +TEST_P(ImportFunctions, + DefinitionShouldBeImportedAsDefintionWhenThereIsAPrototype) { + Decl *FromTU = getTuDecl("void f(); void f() {}", Lang_CXX); + auto Pattern = functionDecl(hasName("f")); + FunctionDecl *FromD = // Definition + LastDeclMatcher().match(FromTU, Pattern); + + Decl *ImportedD = Import(FromD, Lang_CXX); + Decl *ToTU = ImportedD->getTranslationUnitDecl(); + + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), 1u); + EXPECT_TRUE(cast(ImportedD)->doesThisDeclarationHaveABody()); +} + +TEST_P(ImportFunctions, DefinitionShouldBeImportedAsADefinition) { + Decl *FromTU = getTuDecl("void f() {}", Lang_CXX); + auto Pattern = functionDecl(hasName("f")); + FunctionDecl *FromD = + FirstDeclMatcher().match(FromTU, Pattern); + + Decl *ImportedD = Import(FromD, Lang_CXX); + Decl *ToTU = ImportedD->getTranslationUnitDecl(); + + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), 1u); + EXPECT_TRUE(cast(ImportedD)->doesThisDeclarationHaveABody()); +} + +TEST_P(ImportFunctions, DISABLED_ImportPrototypeOfRecursiveFunction) { + Decl *FromTU = getTuDecl("void f(); void f() { f(); }", Lang_CXX); + auto Pattern = functionDecl(hasName("f")); + FunctionDecl *PrototypeFD = + FirstDeclMatcher().match(FromTU, Pattern); + + Decl *ImportedD = Import(PrototypeFD, Lang_CXX); + Decl *ToTU = ImportedD->getTranslationUnitDecl(); + + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), 1u); + EXPECT_TRUE(cast(ImportedD)->doesThisDeclarationHaveABody()); +} + +TEST_P(ImportFunctions, ImportDefinitionOfRecursiveFunction) { + Decl *FromTU = getTuDecl("void f(); void f() { f(); }", Lang_CXX); + auto Pattern = functionDecl(hasName("f")); + FunctionDecl *DefinitionFD = + LastDeclMatcher().match(FromTU, Pattern); + + Decl *ImportedD = Import(DefinitionFD, Lang_CXX); + Decl *ToTU = ImportedD->getTranslationUnitDecl(); + + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), 1u); + EXPECT_TRUE(cast(ImportedD)->doesThisDeclarationHaveABody()); +} + +TEST_P(ImportFunctions, ImportPrototypes) { + auto Pattern = functionDecl(hasName("f")); + + Decl *ImportedD; + { + Decl *FromTU = getTuDecl("void f();", Lang_CXX, "input0.cc"); + FunctionDecl *FromD = + FirstDeclMatcher().match(FromTU, Pattern); + + ImportedD = Import(FromD, Lang_CXX); + } + Decl *ImportedD1; + { + Decl *FromTU = getTuDecl("void f();", Lang_CXX, "input1.cc"); + FunctionDecl *FromD = + FirstDeclMatcher().match(FromTU, Pattern); + ImportedD1 = Import(FromD, Lang_CXX); + } + + Decl *ToTU = ToAST->getASTContext().getTranslationUnitDecl(); + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), 1u); + EXPECT_EQ(ImportedD, ImportedD1); + EXPECT_TRUE(!cast(ImportedD)->doesThisDeclarationHaveABody()); +} + +TEST_P(ImportFunctions, ImportDefinitionThenPrototype) { + auto Pattern = functionDecl(hasName("f")); + + Decl *ImportedD; + { + Decl *FromTU = getTuDecl("void f(){}", Lang_CXX, "input0.cc"); + FunctionDecl *FromD = + FirstDeclMatcher().match(FromTU, Pattern); + + ImportedD = Import(FromD, Lang_CXX); + } + Decl *ImportedD1; + { + Decl *FromTU = getTuDecl("void f();", Lang_CXX, "input1.cc"); + FunctionDecl *FromD = + FirstDeclMatcher().match(FromTU, Pattern); + ImportedD1 = Import(FromD, Lang_CXX); + } + + Decl *ToTU = ToAST->getASTContext().getTranslationUnitDecl(); + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), 1u); + EXPECT_EQ(ImportedD, ImportedD1); + EXPECT_TRUE(cast(ImportedD)->doesThisDeclarationHaveABody()); +} + +TEST_P(ImportFunctions, ImportPrototypeThenDefinition) { + auto Pattern = functionDecl(hasName("f")); + + { + Decl *FromTU = getTuDecl("void f();", Lang_CXX, "input0.cc"); + FunctionDecl *FromD = + FirstDeclMatcher().match(FromTU, Pattern); + + Import(FromD, Lang_CXX); + } + { + Decl *FromTU = getTuDecl("void f(){}", Lang_CXX, "input1.cc"); + FunctionDecl *FromD = + FirstDeclMatcher().match(FromTU, Pattern); + Import(FromD, Lang_CXX); + } + + Decl *ToTU = ToAST->getASTContext().getTranslationUnitDecl(); + ASSERT_EQ(DeclCounter().match(ToTU, Pattern), 2u); + FunctionDecl *ProtoD = FirstDeclMatcher().match(ToTU, Pattern); + EXPECT_TRUE(!ProtoD->doesThisDeclarationHaveABody()); + FunctionDecl *DefinitionD = + LastDeclMatcher().match(ToTU, Pattern); + EXPECT_TRUE(DefinitionD->doesThisDeclarationHaveABody()); + EXPECT_EQ(DefinitionD->getPreviousDecl(), ProtoD); +} + +TEST_P(ImportFunctions, DISABLED_ImportPrototypeThenProtoAndDefinition) { + auto Pattern = functionDecl(hasName("f")); + + { + Decl *FromTU = getTuDecl("void f();", Lang_CXX, "input0.cc"); + FunctionDecl *FromD = + FirstDeclMatcher().match(FromTU, Pattern); + + Import(FromD, Lang_CXX); + } + { + Decl *FromTU = getTuDecl("void f(); void f(){}", Lang_CXX, "input1.cc"); + FunctionDecl *FromD = + FirstDeclMatcher().match(FromTU, Pattern); + Import(FromD, Lang_CXX); + } + + Decl *ToTU = ToAST->getASTContext().getTranslationUnitDecl(); + ASSERT_EQ(DeclCounter().match(ToTU, Pattern), 2u); + FunctionDecl *ProtoD = FirstDeclMatcher().match(ToTU, Pattern); + EXPECT_TRUE(!ProtoD->doesThisDeclarationHaveABody()); + FunctionDecl *DefinitionD = + LastDeclMatcher().match(ToTU, Pattern); + EXPECT_TRUE(DefinitionD->doesThisDeclarationHaveABody()); + EXPECT_EQ(DefinitionD->getPreviousDecl(), ProtoD); +} + +TEST_P(ImportFunctions, OverriddenMethodsShouldBeImported) { + auto Code = + R"( + struct B { virtual void f(); }; + void B::f() {} + struct D : B { void f(); }; + )"; + auto Pattern = + cxxMethodDecl(hasName("f"), hasParent(cxxRecordDecl(hasName("D")))); + Decl *FromTU = getTuDecl(Code, Lang_CXX); + CXXMethodDecl *Proto = + FirstDeclMatcher().match(FromTU, Pattern); + + ASSERT_EQ(Proto->size_overridden_methods(), 1u); + CXXMethodDecl *To = cast(Import(Proto, Lang_CXX)); + EXPECT_EQ(To->size_overridden_methods(), 1u); +} + +TEST_P(ImportFunctions, VirtualFlagShouldBePreservedWhenImportingPrototype) { + auto Code = + R"( + struct B { virtual void f(); }; + void B::f() {} + )"; + auto Pattern = + cxxMethodDecl(hasName("f"), hasParent(cxxRecordDecl(hasName("B")))); + Decl *FromTU = getTuDecl(Code, Lang_CXX); + CXXMethodDecl *Proto = + FirstDeclMatcher().match(FromTU, Pattern); + CXXMethodDecl *Def = LastDeclMatcher().match(FromTU, Pattern); + + ASSERT_TRUE(Proto->isVirtual()); + ASSERT_TRUE(Def->isVirtual()); + CXXMethodDecl *To = cast(Import(Proto, Lang_CXX)); + EXPECT_TRUE(To->isVirtual()); +} + +INSTANTIATE_TEST_CASE_P( + ParameterizedTests, ImportFunctions, + ::testing::Values(ArgVector(), ArgVector{"-fdelayed-template-parsing"}),); + } // end namespace ast_matchers } // end namespace clang Index: unittests/AST/DeclMatcher.h =================================================================== --- unittests/AST/DeclMatcher.h +++ unittests/AST/DeclMatcher.h @@ -0,0 +1,68 @@ +//===- unittest/AST/DeclMatcher.h - AST unit test support ---------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_UNITTESTS_AST_DECLMATCHER_H +#define LLVM_CLANG_UNITTESTS_AST_DECLMATCHER_H + +#include "clang/ASTMatchers/ASTMatchFinder.h" + +namespace clang { +namespace ast_matchers { + +enum class DeclMatcherKind { First, Last }; + +// Matcher class to retrieve the first/last matched node under a given AST. +template +class DeclMatcher : public MatchFinder::MatchCallback { + NodeType *Node = nullptr; + void run(const MatchFinder::MatchResult &Result) override { + if ((MatcherKind == DeclMatcherKind::First && Node == nullptr) || + MatcherKind == DeclMatcherKind::Last) { + Node = const_cast(Result.Nodes.getNodeAs("")); + } + } +public: + // Returns the first/last matched node under the tree rooted in `D`. + template + NodeType *match(const Decl *D, const MatcherType &AMatcher) { + MatchFinder Finder; + Finder.addMatcher(AMatcher.bind(""), this); + Finder.matchAST(D->getASTContext()); + assert(Node); + return Node; + } +}; +template +using LastDeclMatcher = DeclMatcher; +template +using FirstDeclMatcher = DeclMatcher; + +template +class DeclCounter : public MatchFinder::MatchCallback { + unsigned Count = 0; + void run(const MatchFinder::MatchResult &Result) override { + if(Result.Nodes.getNodeAs("")) { + ++Count; + } + } +public: + // Returns the number of matched nodes under the tree rooted in `D`. + template + unsigned match(const Decl *D, const MatcherType &AMatcher) { + MatchFinder Finder; + Finder.addMatcher(AMatcher.bind(""), this); + Finder.matchAST(D->getASTContext()); + return Count; + } +}; + +} // end namespace ast_matchers +} // end namespace clang + +#endif