r330704 - [ASTImporter] Allow testing of import sequences; fix import of typedefs for anonymous decls

Aleksei Sidorin via cfe-commits cfe-commits at lists.llvm.org
Tue Apr 24 03:11:54 PDT 2018


Author: a.sidorin
Date: Tue Apr 24 03:11:53 2018
New Revision: 330704

URL: http://llvm.org/viewvc/llvm-project?rev=330704&view=rev
Log:
[ASTImporter] Allow testing of import sequences; fix import of typedefs for anonymous decls

This patch introduces the ability to test an arbitrary sequence of imports
between a given set of virtual source files. This should finally allow
us to write simple tests and fix annoying issues inside ASTImporter
that cause failures in CSA CTU. This is done by refactoring
ASTImporterTest functions and introducing `testImportSequence` facility.
As a side effect, `testImport` facility was generalized a bit more. It
should now allow import of non-decl AST nodes; however, there is still no
test using this ability.

As a "test for test", there is also a fix for import anonymous TagDecls
referred by typedef. Before this patch, the setting of typedef for anonymous
structure was delayed; however, this approach misses the corner case if
an enum constant is imported directly. In this patch, typedefs for
anonymous declarations are imported right after the anonymous declaration
is imported, without any delay.

Thanks to Adam Balogh for suggestions included into this patch.

Differential Revision: https://reviews.llvm.org/D44079


Modified:
    cfe/trunk/include/clang/AST/ASTImporter.h
    cfe/trunk/lib/AST/ASTImporter.cpp
    cfe/trunk/test/Analysis/Inputs/ctu-other.cpp
    cfe/trunk/test/Analysis/Inputs/externalFnMap.txt
    cfe/trunk/test/Analysis/ctu-main.cpp
    cfe/trunk/unittests/AST/ASTImporterTest.cpp

Modified: cfe/trunk/include/clang/AST/ASTImporter.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/AST/ASTImporter.h?rev=330704&r1=330703&r2=330704&view=diff
==============================================================================
--- cfe/trunk/include/clang/AST/ASTImporter.h (original)
+++ cfe/trunk/include/clang/AST/ASTImporter.h Tue Apr 24 03:11:53 2018
@@ -83,10 +83,6 @@ class TypeSourceInfo;
     ///  the "from" source manager to the corresponding CXXBasesSpecifier
     ///  in the "to" source manager.
     ImportedCXXBaseSpecifierMap ImportedCXXBaseSpecifiers;
-
-    /// \brief Imported, anonymous tag declarations that are missing their 
-    /// corresponding typedefs.
-    SmallVector<TagDecl *, 4> AnonTagsWithPendingTypedefs;
     
     /// \brief Declaration (from, to) pairs that are known not to be equivalent
     /// (which we have already complained about).
@@ -136,6 +132,9 @@ class TypeSourceInfo;
     /// \returns the equivalent declaration in the "to" context, or a NULL type 
     /// if an error occurred.
     Decl *Import(Decl *FromD);
+    Decl *Import(const Decl *FromD) {
+      return Import(const_cast<Decl *>(FromD));
+    }
 
     /// \brief Return the copy of the given declaration in the "to" context if
     /// it has already been imported from the "from" context.  Otherwise return

Modified: cfe/trunk/lib/AST/ASTImporter.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/AST/ASTImporter.cpp?rev=330704&r1=330703&r2=330704&view=diff
==============================================================================
--- cfe/trunk/lib/AST/ASTImporter.cpp (original)
+++ cfe/trunk/lib/AST/ASTImporter.cpp Tue Apr 24 03:11:53 2018
@@ -1088,6 +1088,17 @@ void ASTNodeImporter::ImportDeclContext(
     Importer.Import(From);
 }
 
+static void setTypedefNameForAnonDecl(TagDecl *From, TagDecl *To,
+                               ASTImporter &Importer) {
+  if (TypedefNameDecl *FromTypedef = From->getTypedefNameForAnonDecl()) {
+    auto *ToTypedef =
+      cast_or_null<TypedefNameDecl>(Importer.Import(FromTypedef));
+    assert (ToTypedef && "Failed to import typedef of an anonymous structure");
+
+    To->setTypedefNameForAnonDecl(ToTypedef);
+  }
+}
+
 bool ASTNodeImporter::ImportDefinition(RecordDecl *From, RecordDecl *To, 
                                        ImportDefinitionKind Kind) {
   if (To->getDefinition() || To->isBeingDefined()) {
@@ -1098,6 +1109,8 @@ bool ASTNodeImporter::ImportDefinition(R
   }
   
   To->startDefinition();
+
+  setTypedefNameForAnonDecl(From, To, Importer);
   
   // Add base classes.
   if (auto *ToCXX = dyn_cast<CXXRecordDecl>(To)) {
@@ -1229,6 +1242,8 @@ bool ASTNodeImporter::ImportDefinition(E
   
   To->startDefinition();
 
+  setTypedefNameForAnonDecl(From, To, Importer);
+
   QualType T = Importer.Import(Importer.getFromContext().getTypeDeclType(From));
   if (T.isNull())
     return true;
@@ -1707,6 +1722,11 @@ Decl *ASTNodeImporter::VisitTypedefNameD
   if (T.isNull())
     return nullptr;
 
+  // Some nodes (like anonymous tags referred by typedefs) are allowed to
+  // import their enclosing typedef directly. Check if this is the case.
+  if (Decl *AlreadyImported = Importer.GetAlreadyImportedOrNull(D))
+    return AlreadyImported;
+
   // Create the new typedef node.
   TypeSourceInfo *TInfo = Importer.Import(D->getTypeSourceInfo());
   SourceLocation StartL = Importer.Import(D->getLocStart());
@@ -6576,29 +6596,7 @@ Decl *ASTImporter::Import(Decl *FromD) {
 
   // Record the imported declaration.
   ImportedDecls[FromD] = ToD;
-  
-  if (auto *FromTag = dyn_cast<TagDecl>(FromD)) {
-    // Keep track of anonymous tags that have an associated typedef.
-    if (FromTag->getTypedefNameForAnonDecl())
-      AnonTagsWithPendingTypedefs.push_back(FromTag);
-  } else if (auto *FromTypedef = dyn_cast<TypedefNameDecl>(FromD)) {
-    // When we've finished transforming a typedef, see whether it was the
-    // typedef for an anonymous tag.
-    for (SmallVectorImpl<TagDecl *>::iterator
-               FromTag = AnonTagsWithPendingTypedefs.begin(), 
-            FromTagEnd = AnonTagsWithPendingTypedefs.end();
-         FromTag != FromTagEnd; ++FromTag) {
-      if ((*FromTag)->getTypedefNameForAnonDecl() == FromTypedef) {
-        if (auto *ToTag = cast_or_null<TagDecl>(Import(*FromTag))) {
-          // We found the typedef for an anonymous tag; link them.
-          ToTag->setTypedefNameForAnonDecl(cast<TypedefNameDecl>(ToD));
-          AnonTagsWithPendingTypedefs.erase(FromTag);
-          break;
-        }
-      }
-    }
-  }
-  
+
   return ToD;
 }
 

Modified: cfe/trunk/test/Analysis/Inputs/ctu-other.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/Inputs/ctu-other.cpp?rev=330704&r1=330703&r2=330704&view=diff
==============================================================================
--- cfe/trunk/test/Analysis/Inputs/ctu-other.cpp (original)
+++ cfe/trunk/test/Analysis/Inputs/ctu-other.cpp Tue Apr 24 03:11:53 2018
@@ -65,3 +65,6 @@ int chf1(int x) {
   return chf2(x);
 }
 }
+
+typedef struct { int n; } Anonymous;
+int fun_using_anon_struct(int n) { Anonymous anon; anon.n = n; return anon.n; }

Modified: cfe/trunk/test/Analysis/Inputs/externalFnMap.txt
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/Inputs/externalFnMap.txt?rev=330704&r1=330703&r2=330704&view=diff
==============================================================================
--- cfe/trunk/test/Analysis/Inputs/externalFnMap.txt (original)
+++ cfe/trunk/test/Analysis/Inputs/externalFnMap.txt Tue Apr 24 03:11:53 2018
@@ -11,3 +11,4 @@ c:@F at h#I# ctu-other.cpp.ast
 c:@F at h_chain#I# ctu-chain.cpp.ast
 c:@N at chns@S at chcls@F at chf4#I# ctu-chain.cpp.ast
 c:@N at chns@F at chf2#I# ctu-chain.cpp.ast
+c:@F at fun_using_anon_struct#I# ctu-other.cpp.ast

Modified: cfe/trunk/test/Analysis/ctu-main.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/ctu-main.cpp?rev=330704&r1=330703&r2=330704&view=diff
==============================================================================
--- cfe/trunk/test/Analysis/ctu-main.cpp (original)
+++ cfe/trunk/test/Analysis/ctu-main.cpp Tue Apr 24 03:11:53 2018
@@ -40,6 +40,8 @@ namespace chns {
 int chf1(int x);
 }
 
+int fun_using_anon_struct(int);
+
 int main() {
   clang_analyzer_eval(f(3) == 2); // expected-warning{{TRUE}}
   clang_analyzer_eval(f(4) == 3); // expected-warning{{TRUE}}
@@ -55,4 +57,5 @@ int main() {
   clang_analyzer_eval(mycls::embed_cls2().fecl2(0) == -11); // expected-warning{{TRUE}}
 
   clang_analyzer_eval(chns::chf1(4) == 12); // expected-warning{{TRUE}}
+  clang_analyzer_eval(fun_using_anon_struct(8) == 8); // expected-warning{{TRUE}}
 }

Modified: cfe/trunk/unittests/AST/ASTImporterTest.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/unittests/AST/ASTImporterTest.cpp?rev=330704&r1=330703&r2=330704&view=diff
==============================================================================
--- cfe/trunk/unittests/AST/ASTImporterTest.cpp (original)
+++ cfe/trunk/unittests/AST/ASTImporterTest.cpp Tue Apr 24 03:11:53 2018
@@ -20,10 +20,15 @@
 
 #include "DeclMatcher.h"
 #include "gtest/gtest.h"
+#include "llvm/ADT/StringMap.h"
 
 namespace clang {
 namespace ast_matchers {
 
+using internal::Matcher;
+using internal::BindableMatcher;
+using llvm::StringMap;
+
 typedef std::vector<std::string> ArgVector;
 typedef std::vector<ArgVector> RunOptions;
 
@@ -70,22 +75,61 @@ static RunOptions getRunOptionsForLangua
 
 // 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) {
+static void
+createVirtualFileIfNeeded(ASTUnit *ToAST, StringRef FileName,
+                          std::unique_ptr<llvm::MemoryBuffer> &&Buffer) {
   assert(ToAST);
   ASTContext &ToCtx = ToAST->getASTContext();
   auto *OFS = static_cast<vfs::OverlayFileSystem *>(
       ToCtx.getSourceManager().getFileManager().getVirtualFileSystem().get());
   auto *MFS =
       static_cast<vfs::InMemoryFileSystem *>(OFS->overlays_begin()->get());
-  MFS->addFile(FileName, 0, llvm::MemoryBuffer::getMemBuffer(Code.c_str()));
+  MFS->addFile(FileName, 0, std::move(Buffer));
 }
 
-template<typename NodeType, typename MatcherType>
+static void createVirtualFileIfNeeded(ASTUnit *ToAST, StringRef FileName,
+                                      StringRef Code) {
+  return createVirtualFileIfNeeded(ToAST, FileName,
+                                   llvm::MemoryBuffer::getMemBuffer(Code));
+}
+
+template <typename NodeType>
+NodeType importNode(ASTUnit *From, ASTUnit *To, ASTImporter &Importer,
+                    NodeType Node) {
+  ASTContext &ToCtx = To->getASTContext();
+
+  // Add 'From' file to virtual file system so importer can 'find' it
+  // while importing SourceLocations. It is safe to add same file multiple
+  // times - it just isn't replaced.
+  StringRef FromFileName = From->getMainFileName();
+  createVirtualFileIfNeeded(To, FromFileName,
+                            From->getBufferForFile(FromFileName));
+
+  auto Imported = Importer.Import(Node);
+
+  // This should dump source locations and assert if some source locations
+  // were not imported.
+  SmallString<1024> ImportChecker;
+  llvm::raw_svector_ostream ToNothing(ImportChecker);
+  ToCtx.getTranslationUnitDecl()->print(ToNothing);
+
+  // This traverses the AST to catch certain bugs like poorly or not
+  // implemented subtrees.
+  Imported->dump(ToNothing);
+
+  return Imported;
+}
+
+const StringRef DeclToImportID = "declToImport";
+const StringRef DeclToVerifyID = "declToVerify";
+
+template <typename NodeType>
 testing::AssertionResult
 testImport(const std::string &FromCode, const ArgVector &FromArgs,
            const std::string &ToCode, const ArgVector &ToArgs,
-           MatchVerifier<NodeType> &Verifier, const MatcherType &AMatcher) {
+           MatchVerifier<NodeType> &Verifier,
+           const BindableMatcher<NodeType> &SearchMatcher,
+           const BindableMatcher<NodeType> &VerificationMatcher) {
   const char *const InputFileName = "input.cc";
   const char *const OutputFileName = "output.cc";
 
@@ -97,46 +141,47 @@ testImport(const std::string &FromCode,
   ASTContext &FromCtx = FromAST->getASTContext(),
       &ToCtx = ToAST->getASTContext();
 
-  // Add input.cc to virtual file system so importer can 'find' it
-  // while importing SourceLocations.
-  createVirtualFileIfNeeded(ToAST.get(), InputFileName, FromCode);
-
   ASTImporter Importer(ToCtx, ToAST->getFileManager(),
                        FromCtx, FromAST->getFileManager(), false);
 
-  IdentifierInfo *ImportedII = &FromCtx.Idents.get("declToImport");
-  assert(ImportedII && "Declaration with 'declToImport' name"
-                       "should be specified in test!");
-  DeclarationName ImportDeclName(ImportedII);
-  SmallVector<NamedDecl *, 4> FoundDecls;
-  FromCtx.getTranslationUnitDecl()->localUncachedLookup(
-        ImportDeclName, FoundDecls);
-
-  if (FoundDecls.size() != 1)
-    return testing::AssertionFailure() << "Multiple declarations were found!";
+  auto FoundNodes = match(SearchMatcher, FromCtx);
+  if (FoundNodes.size() != 1)
+    return testing::AssertionFailure()
+           << "Multiple potential nodes were found!";
+
+  auto ToImport = selectFirst<NodeType>(DeclToImportID, FoundNodes);
+  if (!ToImport)
+    return testing::AssertionFailure() << "Node type mismatch!";
 
   // Sanity check: the node being imported should match in the same way as
   // the result node.
-  EXPECT_TRUE(Verifier.match(FoundDecls.front(), AMatcher));
+  BindableMatcher<NodeType> WrapperMatcher(VerificationMatcher);
+  EXPECT_TRUE(Verifier.match(ToImport, WrapperMatcher));
 
-  auto Imported = Importer.Import(FoundDecls.front());
+  auto Imported = importNode(FromAST.get(), ToAST.get(), Importer, ToImport);
   if (!Imported)
     return testing::AssertionFailure() << "Import failed, nullptr returned!";
 
-  // This should dump source locations and assert if some source locations
-  // were not imported.
-  SmallString<1024> ImportChecker;
-  llvm::raw_svector_ostream ToNothing(ImportChecker);
-  ToCtx.getTranslationUnitDecl()->print(ToNothing);
-
-  // This traverses the AST to catch certain bugs like poorly or not
-  // implemented subtrees.
-  Imported->dump(ToNothing);
+  return Verifier.match(Imported, WrapperMatcher);
+}
 
-  return Verifier.match(Imported, AMatcher);
+template <typename NodeType>
+testing::AssertionResult
+testImport(const std::string &FromCode, const ArgVector &FromArgs,
+           const std::string &ToCode, const ArgVector &ToArgs,
+           MatchVerifier<NodeType> &Verifier,
+           const BindableMatcher<NodeType> &VerificationMatcher) {
+  return testImport(
+      FromCode, FromArgs, ToCode, ToArgs, Verifier,
+      translationUnitDecl(
+          has(namedDecl(hasName(DeclToImportID)).bind(DeclToImportID))),
+      VerificationMatcher);
 }
 
-template<typename NodeType, typename MatcherType>
+/// Test how AST node named "declToImport" located in the translation unit
+/// of "FromCode" virtual file is imported to "ToCode" virtual file.
+/// The verification is done by running AMatcher over the imported node.
+template <typename NodeType, typename MatcherType>
 void testImport(const std::string &FromCode, Language FromLang,
                 const std::string &ToCode, Language ToLang,
                 MatchVerifier<NodeType> &Verifier,
@@ -149,8 +194,6 @@ void testImport(const std::string &FromC
                              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.
@@ -164,8 +207,8 @@ class ASTImporterTestBase : public ::tes
 
   struct TU {
     // Buffer for the context, must live in the test scope.
-    std::string Code;
-    std::string FileName;
+    StringRef Code;
+    StringRef FileName;
     std::unique_ptr<ASTUnit> Unit;
     TranslationUnitDecl *TUDecl = nullptr;
     TU(StringRef Code, StringRef FileName, ArgVector Args)
@@ -297,16 +340,121 @@ public:
   }
 };
 
-AST_MATCHER_P(RecordDecl, hasFieldOrder, std::vector<StringRef>, Order) {
-  size_t Index = 0;
-  for (FieldDecl *Field : Node.fields()) {
-    if (Index == Order.size())
-      return false;
-    if (Field->getName() != Order[Index])
-      return false;
-    ++Index;
+
+struct ImportAction {
+  StringRef FromFilename;
+  StringRef ToFilename;
+  // FIXME: Generalize this to support other node kinds.
+  BindableMatcher<Decl> ImportPredicate;
+
+  ImportAction(StringRef FromFilename, StringRef ToFilename,
+               DeclarationMatcher ImportPredicate)
+      : FromFilename(FromFilename), ToFilename(ToFilename),
+        ImportPredicate(ImportPredicate) {}
+
+  ImportAction(StringRef FromFilename, StringRef ToFilename,
+               const std::string &DeclName)
+      : FromFilename(FromFilename), ToFilename(ToFilename),
+        ImportPredicate(namedDecl(hasName(DeclName))) {}
+};
+
+using SingleASTUnitForAllOpts = std::vector<std::unique_ptr<ASTUnit>>;
+using AllASTUnitsForAllOpts = StringMap<SingleASTUnitForAllOpts>;
+
+struct CodeEntry {
+  std::string CodeSample;
+  Language Lang;
+
+  /// Builds N copies of ASTUnits for each potential compile options set
+  /// for further import actions. N is equal to size of this option set.
+  SingleASTUnitForAllOpts createASTUnits(StringRef FileName) const {
+    auto RunOpts = getRunOptionsForLanguage(Lang);
+    size_t NumOpts = RunOpts.size();
+    SingleASTUnitForAllOpts ResultASTs(NumOpts);
+    for (size_t CompileOpt = 0; CompileOpt < NumOpts; ++CompileOpt) {
+      auto AST = tooling::buildASTFromCodeWithArgs(
+          CodeSample, RunOpts[CompileOpt], FileName);
+      EXPECT_TRUE(AST.get());
+      ResultASTs[CompileOpt] = std::move(AST);
+    }
+    return ResultASTs;
+  }
+};
+
+using CodeFiles = StringMap<CodeEntry>;
+
+/// Test an arbitrary sequence of imports for a set of given in-memory files.
+/// The verification is done by running VerificationMatcher against a specified
+/// AST node inside of one of given files.
+/// \param CodeSamples Map whose key is the file name and the value is the file
+/// content.
+/// \param ImportActions Sequence of imports. Each import in sequence
+/// specifies "from file" and "to file" and a matcher that is used for
+/// searching a declaration for import in "from file".
+/// \param FileForFinalCheck Name of virtual file for which the final check is
+/// applied.
+/// \param FinalSelectPredicate Matcher that specifies the AST node in the
+/// FileForFinalCheck for which the verification will be done.
+/// \param VerificationMatcher Matcher that will be used for verification after
+/// all imports in sequence are done.
+void testImportSequence(const CodeFiles &CodeSamples,
+                        const std::vector<ImportAction> &ImportActions,
+                        StringRef FileForFinalCheck,
+                        BindableMatcher<Decl> FinalSelectPredicate,
+                        BindableMatcher<Decl> VerificationMatcher) {
+  AllASTUnitsForAllOpts AllASTUnits;
+  using ImporterKey = std::pair<const ASTUnit *, const ASTUnit *>;
+  llvm::DenseMap<ImporterKey, std::unique_ptr<ASTImporter>> Importers;
+
+  auto GenASTsIfNeeded = [&AllASTUnits, &CodeSamples](StringRef Filename) {
+    if (!AllASTUnits.count(Filename)) {
+      auto Found = CodeSamples.find(Filename);
+      assert(Found != CodeSamples.end() && "Wrong file for import!");
+      AllASTUnits[Filename] = Found->getValue().createASTUnits(Filename);
+    }
+  };
+
+  size_t NumCompileOpts = 0;
+  for (const ImportAction &Action : ImportActions) {
+    StringRef FromFile = Action.FromFilename, ToFile = Action.ToFilename;
+    GenASTsIfNeeded(FromFile);
+    GenASTsIfNeeded(ToFile);
+    NumCompileOpts = AllASTUnits[FromFile].size();
+
+    for (size_t CompileOpt = 0; CompileOpt < NumCompileOpts; ++CompileOpt) {
+      ASTUnit *From = AllASTUnits[FromFile][CompileOpt].get();
+      ASTUnit *To = AllASTUnits[ToFile][CompileOpt].get();
+
+      // Create a new importer if needed.
+      std::unique_ptr<ASTImporter> &ImporterRef = Importers[{From, To}];
+      if (!ImporterRef)
+        ImporterRef.reset(new ASTImporter(
+            To->getASTContext(), To->getFileManager(), From->getASTContext(),
+            From->getFileManager(), false));
+
+      // Find the declaration and import it.
+      auto FoundDecl = match(Action.ImportPredicate.bind(DeclToImportID),
+                             From->getASTContext());
+      EXPECT_TRUE(FoundDecl.size() == 1);
+      const Decl *ToImport = selectFirst<Decl>(DeclToImportID, FoundDecl);
+      auto Imported = importNode(From, To, *ImporterRef, ToImport);
+      EXPECT_TRUE(Imported);
+    }
+  }
+
+  // NOTE: We don't do cross-option import check here due to fast growth of
+  // potential option sets.
+  for (size_t CompileOpt = 0; CompileOpt < NumCompileOpts; ++CompileOpt) {
+    // Find the declaration and import it.
+    auto FoundDecl =
+        match(FinalSelectPredicate.bind(DeclToVerifyID),
+              AllASTUnits[FileForFinalCheck][CompileOpt]->getASTContext());
+    EXPECT_TRUE(FoundDecl.size() == 1);
+    const Decl *ToVerify = selectFirst<Decl>(DeclToVerifyID, FoundDecl);
+    MatchVerifier<Decl> Verifier;
+    EXPECT_TRUE(Verifier.match(ToVerify,
+                               BindableMatcher<Decl>(VerificationMatcher)));
   }
-  return Index == Order.size();
 }
 
 TEST(ImportExpr, ImportStringLiteral) {
@@ -1127,6 +1275,18 @@ TEST_P(
       MatchVerifier<Decl>{}.match(To->getTranslationUnitDecl(), Pattern));
 }
 
+AST_MATCHER_P(RecordDecl, hasFieldOrder, std::vector<StringRef>, 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_P(ASTImporterTestBase,
        TUshouldContainClassTemplateSpecializationOfExplicitInstantiation) {
   Decl *From, *To;
@@ -1503,5 +1663,51 @@ INSTANTIATE_TEST_CASE_P(
     ParameterizedTests, ImportFunctions,
     ::testing::Values(ArgVector(), ArgVector{"-fdelayed-template-parsing"}),);
 
+AST_MATCHER_P(TagDecl, hasTypedefForAnonDecl, Matcher<TypedefNameDecl>,
+              InnerMatcher) {
+  if (auto *Typedef = Node.getTypedefNameForAnonDecl())
+    return InnerMatcher.matches(*Typedef, Finder, Builder);
+  return false;
+}
+
+TEST(ImportDecl, ImportEnumSequential) {
+  CodeFiles Samples{{"main.c",
+                     {"void foo();"
+                      "void moo();"
+                      "int main() { foo(); moo(); }",
+                      Lang_C}},
+
+                    {"foo.c",
+                     {"typedef enum { THING_VALUE } thing_t;"
+                      "void conflict(thing_t type);"
+                      "void foo() { (void)THING_VALUE; }"
+                      "void conflict(thing_t type) {}",
+                      Lang_C}},
+
+                    {"moo.c",
+                     {"typedef enum { THING_VALUE } thing_t;"
+                      "void conflict(thing_t type);"
+                      "void moo() { conflict(THING_VALUE); }",
+                      Lang_C}}};
+
+  auto VerificationMatcher =
+      enumDecl(has(enumConstantDecl(hasName("THING_VALUE"))),
+               hasTypedefForAnonDecl(hasName("thing_t")));
+
+  ImportAction ImportFoo{"foo.c", "main.c", functionDecl(hasName("foo"))},
+      ImportMoo{"moo.c", "main.c", functionDecl(hasName("moo"))};
+
+  testImportSequence(
+      Samples, {ImportFoo, ImportMoo}, // "foo", them "moo".
+      // Just check that there is only one enum decl in the result AST.
+      "main.c", enumDecl(), VerificationMatcher);
+
+  // For different import order, result should be the same.
+  testImportSequence(
+      Samples, {ImportMoo, ImportFoo}, // "moo", them "foo".
+      // Check that there is only one enum decl in the result AST.
+      "main.c", enumDecl(), VerificationMatcher);
+}
+
 } // end namespace ast_matchers
 } // end namespace clang




More information about the cfe-commits mailing list