Index: lib/AST/ASTImporter.cpp =================================================================== --- lib/AST/ASTImporter.cpp +++ lib/AST/ASTImporter.cpp @@ -2164,11 +2164,21 @@ } Decl *ASTNodeImporter::VisitRecordDecl(RecordDecl *D) { + bool IsFriendTemplate = false; + if (auto *DCXX = dyn_cast(D)) { + IsFriendTemplate = + DCXX->getDescribedClassTemplate() && + DCXX->getDescribedClassTemplate()->getFriendObjectKind() != + Decl::FOK_None; + } + // If this record has a definition in the translation unit we're coming from, // but this particular declaration is not that definition, import the // definition and map to that. TagDecl *Definition = D->getDefinition(); if (Definition && Definition != D && + // Friend template declaration must be imported on its own. + !IsFriendTemplate && // In contrast to a normal CXXRecordDecl, the implicit // CXXRecordDecl of ClassTemplateSpecializationDecl is its redeclaration. // The definition of the implicit CXXRecordDecl in this case is the @@ -2241,7 +2251,7 @@ PrevDecl = FoundRecord; if (RecordDecl *FoundDef = FoundRecord->getDefinition()) { - if ((SearchName && !D->isCompleteDefinition()) + if ((SearchName && !D->isCompleteDefinition() && !IsFriendTemplate) || (D->isCompleteDefinition() && D->isAnonymousStructOrUnion() == FoundDef->isAnonymousStructOrUnion() && @@ -2281,6 +2291,9 @@ !IsStructuralMatch(D, FoundRecord)) continue; + if (IsFriendTemplate) + continue; + AdoptDecl = FoundRecord; continue; } else if (!SearchName) { @@ -2348,7 +2361,7 @@ if (!ToDescribed) return nullptr; D2CXX->setDescribedClassTemplate(ToDescribed); - if (!DCXX->isInjectedClassName()) { + if (!DCXX->isInjectedClassName() && !IsFriendTemplate) { // In a record describing a template the type should be an // InjectedClassNameType (see Sema::CheckClassTemplate). Update the // previously set type to the correct value here (ToDescribed is not @@ -4371,12 +4384,14 @@ } Decl *ASTNodeImporter::VisitClassTemplateDecl(ClassTemplateDecl *D) { + bool IsFriend = D->getFriendObjectKind() != Decl::FOK_None; + // If this record has a definition in the translation unit we're coming from, // but this particular declaration is not that definition, import the // definition and map to that. auto *Definition = cast_or_null(D->getTemplatedDecl()->getDefinition()); - if (Definition && Definition != D->getTemplatedDecl()) { + if (Definition && Definition != D->getTemplatedDecl() && !IsFriend) { Decl *ImportedDef = Importer.Import(Definition->getDescribedClassTemplate()); if (!ImportedDef) @@ -4413,17 +4428,20 @@ // definition. So, try to get the definition if that is available in // the redecl chain. ClassTemplateDecl *TemplateWithDef = getDefinition(FoundTemplate); - if (!TemplateWithDef) + if (TemplateWithDef) + FoundTemplate = TemplateWithDef; + else continue; - FoundTemplate = TemplateWithDef; // Continue with the definition. } if (IsStructuralMatch(D, FoundTemplate)) { - // The class templates structurally match; call it the same template. + if (!IsFriend) { + Importer.MapImported(D->getTemplatedDecl(), + FoundTemplate->getTemplatedDecl()); + return Importer.MapImported(D, FoundTemplate); + } - Importer.MapImported(D->getTemplatedDecl(), - FoundTemplate->getTemplatedDecl()); - return Importer.MapImported(D, FoundTemplate); + continue; } } @@ -4461,9 +4479,17 @@ ToTemplated->setDescribedClassTemplate(D2); + if (ToTemplated->getPreviousDecl()) { + assert( + ToTemplated->getPreviousDecl()->getDescribedClassTemplate() && + "Missing described template"); + D2->setPreviousDecl( + ToTemplated->getPreviousDecl()->getDescribedClassTemplate()); + } D2->setAccess(D->getAccess()); D2->setLexicalDeclContext(LexicalDC); - LexicalDC->addDeclInternal(D2); + if (!IsFriend) + LexicalDC->addDeclInternal(D2); if (FromTemplated->isCompleteDefinition() && !ToTemplated->isCompleteDefinition()) { Index: unittests/AST/ASTImporterTest.cpp =================================================================== --- unittests/AST/ASTImporterTest.cpp +++ unittests/AST/ASTImporterTest.cpp @@ -2683,6 +2683,93 @@ EXPECT_EQ(FromIndex, 3u); } +TEST_P( + ASTImporterTestBase, + ImportOfFriendRecordDoesNotMergeDefinition) { + Decl *FromTU = getTuDecl( + R"( + class A { + template class F {}; + class X { + template friend class F; + }; + }; + )", + Lang_CXX, "input0.cc"); + + auto *FromClass = FirstDeclMatcher().match( + FromTU, cxxRecordDecl(hasName("F"), isDefinition())); + auto *FromFriendClass = LastDeclMatcher().match( + FromTU, cxxRecordDecl(hasName("F"))); + + ASSERT_TRUE(FromClass); + ASSERT_TRUE(FromFriendClass); + ASSERT_NE(FromClass, FromFriendClass); + ASSERT_EQ(FromFriendClass->getDefinition(), FromClass); + ASSERT_EQ(FromFriendClass->getPreviousDecl(), FromClass); + ASSERT_EQ( + FromFriendClass->getDescribedClassTemplate()->getPreviousDecl(), + FromClass->getDescribedClassTemplate()); + + auto *ToClass = cast(Import(FromClass, Lang_CXX)); + auto *ToFriendClass = cast(Import(FromFriendClass, Lang_CXX)); + + ASSERT_TRUE(ToClass); + ASSERT_TRUE(ToFriendClass); + EXPECT_NE(ToClass, ToFriendClass); + EXPECT_EQ(ToFriendClass->getDefinition(), ToClass); + ASSERT_EQ(ToFriendClass->getPreviousDecl(), ToClass); + ASSERT_EQ( + ToFriendClass->getDescribedClassTemplate()->getPreviousDecl(), + ToClass->getDescribedClassTemplate()); +} + +TEST_P( + ASTImporterTestBase, + ImportOfRecursiveFriendClass) { + Decl *FromTu = getTuDecl( + R"( + class declToImport { + friend class declToImport; + }; + )", + Lang_CXX, "input.cc"); + + auto *FromD = FirstDeclMatcher().match( + FromTu, cxxRecordDecl(hasName("declToImport"))); + auto *ToD = Import(FromD, Lang_CXX); + auto Pattern = cxxRecordDecl(hasName("declToImport"), has(friendDecl())); + ASSERT_TRUE(MatchVerifier{}.match(FromD, Pattern)); + EXPECT_TRUE(MatchVerifier{}.match(ToD, Pattern)); +} + +TEST_P( + ASTImporterTestBase, + ImportOfRecursiveFriendClassTemplate) { + Decl *FromTu = getTuDecl( + R"( + template class declToImport { + template friend class declToImport; + }; + )", + Lang_CXX, "input.cc"); + + auto *FromD = FirstDeclMatcher().match( + FromTu, classTemplateDecl(hasName("declToImport"))); + auto *ToD = Import(FromD, Lang_CXX); + + auto Pattern = classTemplateDecl( + has(cxxRecordDecl(has(friendDecl(has(classTemplateDecl())))))); + ASSERT_TRUE(MatchVerifier{}.match(FromD, Pattern)); + EXPECT_TRUE(MatchVerifier{}.match(ToD, Pattern)); + + auto *Class = + FirstDeclMatcher().match(ToD, classTemplateDecl()); + auto *Friend = FirstDeclMatcher().match(ToD, friendDecl()); + EXPECT_NE(Friend->getFriendDecl(), Class); + EXPECT_EQ(Friend->getFriendDecl()->getPreviousDecl(), Class); +} + struct DeclContextTest : ASTImporterTestBase {}; TEST_P(DeclContextTest, removeDeclOfClassTemplateSpecialization) {