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 @@ -3632,6 +3632,38 @@ return ToIndirectField; } +static std::tuple +getFriendCountAndPosition(FriendDecl *FD) { + unsigned int FriendCount = 0; + llvm::Optional FriendPosition; + auto *RD = cast(FD->getLexicalDeclContext()); + if (FD->getFriendType()) { + QualType TypeOfFriend = FD->getFriendType()->getType().getCanonicalType(); + for (FriendDecl *FoundFriend : RD->friends()) { + if (FoundFriend == FD) { + FriendPosition = FriendCount; + ++FriendCount; + } else if (FoundFriend->getFriendType() && + FoundFriend->getFriendType()->getType().getCanonicalType() == + TypeOfFriend) + ++FriendCount; + } + } else { + const Decl *CanDeclOfFriend = FD->getFriendDecl()->getCanonicalDecl(); + for (FriendDecl *FoundFriend : RD->friends()) { + if (FoundFriend == FD) { + FriendPosition = FriendCount; + ++FriendCount; + } else if (FoundFriend->getFriendDecl() && + FoundFriend->getFriendDecl()->getCanonicalDecl() == + CanDeclOfFriend) + ++FriendCount; + } + } + assert(FriendPosition && "Friend decl not found in own parent."); + return std::make_tuple(FriendCount, *FriendPosition); +} + ExpectedDecl ASTNodeImporter::VisitFriendDecl(FriendDecl *D) { // Import the major distinguishing characteristics of a declaration. DeclContext *DC, *LexicalDC; @@ -3640,25 +3672,38 @@ // Determine whether we've already imported this decl. // FriendDecl is not a NamedDecl so we cannot use lookup. + // We try to maintain order and count of redundant friend declarations. auto *RD = cast(DC); FriendDecl *ImportedFriend = RD->getFirstFriend(); + SmallVector ImportedEquivalentFriends; while (ImportedFriend) { + bool Match = false; if (D->getFriendDecl() && ImportedFriend->getFriendDecl()) { - if (IsStructuralMatch(D->getFriendDecl(), ImportedFriend->getFriendDecl(), - /*Complain=*/false)) - return Importer.MapImported(D, ImportedFriend); - + Match = + IsStructuralMatch(D->getFriendDecl(), ImportedFriend->getFriendDecl(), + /*Complain=*/false); } else if (D->getFriendType() && ImportedFriend->getFriendType()) { - if (Importer.IsStructurallyEquivalent( - D->getFriendType()->getType(), - ImportedFriend->getFriendType()->getType(), true)) - return Importer.MapImported(D, ImportedFriend); + Match = Importer.IsStructurallyEquivalent( + D->getFriendType()->getType(), + ImportedFriend->getFriendType()->getType(), /*Complain=*/false); } + if (Match) + ImportedEquivalentFriends.push_back(ImportedFriend); + ImportedFriend = ImportedFriend->getNextFriend(); } + std::tuple CountAndPosition = + getFriendCountAndPosition(D); + + assert(ImportedEquivalentFriends.size() <= std::get<0>(CountAndPosition) && + "Class with non-matching friends is imported, ODR check wrong?"); + if (ImportedEquivalentFriends.size() == std::get<0>(CountAndPosition)) + return Importer.MapImported( + D, ImportedEquivalentFriends[std::get<1>(CountAndPosition)]); // Not found. Create it. + // The declarations will be put into order later by ImportDeclContext. FriendDecl::FriendUnion ToFU; if (NamedDecl *FriendD = D->getFriendDecl()) { NamedDecl *ToFriendD; 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 @@ -4005,6 +4005,56 @@ EXPECT_EQ(ImportedFwd, ImportedDef->getPreviousDecl()); } +TEST_P(ImportFriendClasses, ImportOfRepeatedFriendType) { + auto Code = + R"( + class Container { + friend class X; + friend class X; + }; + )"; + Decl *ToTu = getToTuDecl(Code, Lang_CXX); + Decl *FromTu = getTuDecl(Code, Lang_CXX, "from.cc"); + + auto *ToFriend1 = FirstDeclMatcher().match(ToTu, friendDecl()); + auto *ToFriend2 = LastDeclMatcher().match(ToTu, friendDecl()); + auto *FromFriend1 = + FirstDeclMatcher().match(FromTu, friendDecl()); + auto *FromFriend2 = LastDeclMatcher().match(FromTu, friendDecl()); + + FriendDecl *ToImportedFriend1 = Import(FromFriend1, Lang_CXX); + FriendDecl *ToImportedFriend2 = Import(FromFriend2, Lang_CXX); + + EXPECT_NE(ToImportedFriend1, ToImportedFriend2); + EXPECT_EQ(ToFriend1, ToImportedFriend1); + EXPECT_EQ(ToFriend2, ToImportedFriend2); +} + +TEST_P(ImportFriendClasses, ImportOfRepeatedFriendDecl) { + auto Code = + R"( + class Container { + friend void f(); + friend void f(); + }; + )"; + Decl *ToTu = getToTuDecl(Code, Lang_CXX); + Decl *FromTu = getTuDecl(Code, Lang_CXX, "from.cc"); + + auto *ToFriend1 = FirstDeclMatcher().match(ToTu, friendDecl()); + auto *ToFriend2 = LastDeclMatcher().match(ToTu, friendDecl()); + auto *FromFriend1 = + FirstDeclMatcher().match(FromTu, friendDecl()); + auto *FromFriend2 = LastDeclMatcher().match(FromTu, friendDecl()); + + FriendDecl *ToImportedFriend1 = Import(FromFriend1, Lang_CXX); + FriendDecl *ToImportedFriend2 = Import(FromFriend2, Lang_CXX); + + EXPECT_NE(ToImportedFriend1, ToImportedFriend2); + EXPECT_EQ(ToFriend1, ToImportedFriend1); + EXPECT_EQ(ToFriend2, ToImportedFriend2); +} + TEST_P(ASTImporterOptionSpecificTestBase, FriendFunInClassTemplate) { auto *Code = R"( template diff --git a/clang/unittests/AST/StructuralEquivalenceTest.cpp b/clang/unittests/AST/StructuralEquivalenceTest.cpp --- a/clang/unittests/AST/StructuralEquivalenceTest.cpp +++ b/clang/unittests/AST/StructuralEquivalenceTest.cpp @@ -797,6 +797,20 @@ EXPECT_FALSE(testStructuralMatch(t)); } +TEST_F(StructuralEquivalenceRecordTest, SameFriendMultipleTimes) { + auto t = makeNamedDecls("struct foo{ friend class X; };", + "struct foo{ friend class X; friend class X; };", + Lang_CXX); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceRecordTest, SameFriendsDifferentOrder) { + auto t = makeNamedDecls("struct foo{ friend class X; friend class Y; };", + "struct foo{ friend class Y; friend class X; };", + Lang_CXX); + EXPECT_FALSE(testStructuralMatch(t)); +} + struct StructuralEquivalenceLambdaTest : StructuralEquivalenceTest {}; TEST_F(StructuralEquivalenceLambdaTest, LambdaClassesWithDifferentMethods) {