Index: include/clang/AST/DeclContextInternals.h =================================================================== --- include/clang/AST/DeclContextInternals.h +++ include/clang/AST/DeclContextInternals.h @@ -122,6 +122,13 @@ == Vec.end() && "list still contains decl"); } + bool containsInVector(NamedDecl *D) { + assert(getAsVector()); + DeclsTy &Vec = *getAsVector(); + DeclsTy::iterator I = std::find(Vec.begin(), Vec.end(), D); + return I != Vec.end(); + } + /// Remove any declarations which were imported from an external /// AST source. void removeExternalDecls() { Index: include/clang/ASTMatchers/ASTMatchers.h =================================================================== --- include/clang/ASTMatchers/ASTMatchers.h +++ include/clang/ASTMatchers/ASTMatchers.h @@ -1158,6 +1158,17 @@ /// matches 'm'. extern const internal::VariadicDynCastAllOfMatcher fieldDecl; +/// Matches indirect field declarations. +/// +/// Given +/// \code +/// struct X { struct { int a; }; }; +/// \endcode +/// fieldDecl() +/// matches 'a'. +extern const internal::VariadicDynCastAllOfMatcher + indirectFieldDecl; + /// Matches function declarations. /// /// Example matches f Index: lib/AST/ASTImporter.cpp =================================================================== --- lib/AST/ASTImporter.cpp +++ lib/AST/ASTImporter.cpp @@ -122,6 +122,8 @@ return getCanonicalForwardRedeclChain(FD); if (auto *VD = dyn_cast(D)) return getCanonicalForwardRedeclChain(VD); + if (auto *TD = dyn_cast(D)) + return getCanonicalForwardRedeclChain(TD); llvm_unreachable("Bad declaration kind"); } @@ -2596,10 +2598,9 @@ return std::move(Err); IDNS = Decl::IDNS_Ordinary; } else if (Importer.getToContext().getLangOpts().CPlusPlus) - IDNS |= Decl::IDNS_Ordinary; + IDNS |= Decl::IDNS_Ordinary | Decl::IDNS_TagFriend; // We may already have a record of the same name; try to find and match it. - RecordDecl *AdoptDecl = nullptr; RecordDecl *PrevDecl = nullptr; if (!DC->isFunctionOrMethod()) { SmallVector ConflictingDecls; @@ -2632,26 +2633,22 @@ } if (auto *FoundRecord = dyn_cast(Found)) { - if (!SearchName) { + // Do not emit false positive diagnostic in case of unnamed + // struct/union and in case of anonymous structs. Would be false + // becasue there may be several anonymous/unnamed structs in a class. + // E.g. these are both valid: + // struct A { // unnamed structs + // struct { struct A *next; } entry0; + // struct { struct A *next; } entry1; + // }; + // struct X { struct { int a; }; struct { int b; }; }; // anon structs + if (!SearchName) if (!IsStructuralMatch(D, FoundRecord, false)) continue; - } else { - if (!IsStructuralMatch(D, FoundRecord)) { - ConflictingDecls.push_back(FoundDecl); - continue; - } - } - PrevDecl = FoundRecord; - - if (RecordDecl *FoundDef = FoundRecord->getDefinition()) { - if ((SearchName && !D->isCompleteDefinition() && !IsFriendTemplate) - || (D->isCompleteDefinition() && - D->isAnonymousStructOrUnion() - == FoundDef->isAnonymousStructOrUnion())) { - // The record types structurally match, or the "from" translation - // unit only had a forward declaration anyway; call it the same - // function. + if (IsStructuralMatch(D, FoundRecord)) { + RecordDecl *FoundDef = FoundRecord->getDefinition(); + if (D->isThisDeclarationADefinition() && FoundDef) { // FIXME: Structural equivalence check should check for same // user-defined methods. Importer.MapImported(D, FoundDef); @@ -2659,46 +2656,20 @@ auto *FoundCXX = dyn_cast(FoundDef); assert(FoundCXX && "Record type mismatch"); - if (D->isCompleteDefinition() && !Importer.isMinimalImport()) + if (!Importer.isMinimalImport()) // FoundDef may not have every implicit method that D has // because implicit methods are created only if they are used. if (Error Err = ImportImplicitMethods(DCXX, FoundCXX)) return std::move(Err); } - return FoundDef; } - if (IsFriendTemplate) - continue; - } else if (!D->isCompleteDefinition()) { - // We have a forward declaration of this type, so adopt that forward - // declaration rather than building a new one. - - // If one or both can be completed from external storage then try one - // last time to complete and compare them before doing this. - - if (FoundRecord->hasExternalLexicalStorage() && - !FoundRecord->isCompleteDefinition()) - FoundRecord->getASTContext().getExternalSource()->CompleteType(FoundRecord); - if (D->hasExternalLexicalStorage()) - D->getASTContext().getExternalSource()->CompleteType(D); - - if (FoundRecord->isCompleteDefinition() && - D->isCompleteDefinition() && - !IsStructuralMatch(D, FoundRecord)) { - ConflictingDecls.push_back(FoundDecl); - continue; - } - - AdoptDecl = FoundRecord; - continue; + PrevDecl = FoundRecord; + break; } - - continue; - } else if (isa(Found)) - continue; + } ConflictingDecls.push_back(FoundDecl); - } + } // for if (!ConflictingDecls.empty() && SearchName) { Name = Importer.HandleNameConflict(Name, DC, IDNS, @@ -2714,79 +2685,92 @@ return BeginLocOrErr.takeError(); // Create the record declaration. - RecordDecl *D2 = AdoptDecl; - if (!D2) { - CXXRecordDecl *D2CXX = nullptr; - if (auto *DCXX = dyn_cast(D)) { - if (DCXX->isLambda()) { - auto TInfoOrErr = import(DCXX->getLambdaTypeInfo()); - if (!TInfoOrErr) - return TInfoOrErr.takeError(); - if (GetImportedOrCreateSpecialDecl( - D2CXX, CXXRecordDecl::CreateLambda, D, Importer.getToContext(), - DC, *TInfoOrErr, Loc, DCXX->isDependentLambda(), - DCXX->isGenericLambda(), DCXX->getLambdaCaptureDefault())) - return D2CXX; - ExpectedDecl CDeclOrErr = import(DCXX->getLambdaContextDecl()); - if (!CDeclOrErr) - return CDeclOrErr.takeError(); - D2CXX->setLambdaMangling(DCXX->getLambdaManglingNumber(), *CDeclOrErr); - } else if (DCXX->isInjectedClassName()) { - // We have to be careful to do a similar dance to the one in - // Sema::ActOnStartCXXMemberDeclarations - CXXRecordDecl *const PrevDecl = nullptr; - const bool DelayTypeCreation = true; - if (GetImportedOrCreateDecl(D2CXX, D, Importer.getToContext(), - D->getTagKind(), DC, *BeginLocOrErr, Loc, - Name.getAsIdentifierInfo(), PrevDecl, - DelayTypeCreation)) - return D2CXX; - Importer.getToContext().getTypeDeclType( - D2CXX, dyn_cast(DC)); - } else { - if (GetImportedOrCreateDecl(D2CXX, D, Importer.getToContext(), - D->getTagKind(), DC, *BeginLocOrErr, Loc, - Name.getAsIdentifierInfo(), - cast_or_null(PrevDecl))) - return D2CXX; - } + RecordDecl *D2 = nullptr; + CXXRecordDecl *D2CXX = nullptr; + if (auto *DCXX = dyn_cast(D)) { + if (DCXX->isLambda()) { + auto TInfoOrErr = import(DCXX->getLambdaTypeInfo()); + if (!TInfoOrErr) + return TInfoOrErr.takeError(); + if (GetImportedOrCreateSpecialDecl( + D2CXX, CXXRecordDecl::CreateLambda, D, Importer.getToContext(), + DC, *TInfoOrErr, Loc, DCXX->isDependentLambda(), + DCXX->isGenericLambda(), DCXX->getLambdaCaptureDefault())) + return D2CXX; + ExpectedDecl CDeclOrErr = import(DCXX->getLambdaContextDecl()); + if (!CDeclOrErr) + return CDeclOrErr.takeError(); + D2CXX->setLambdaMangling(DCXX->getLambdaManglingNumber(), *CDeclOrErr); + } else if (DCXX->isInjectedClassName()) { + // We have to be careful to do a similar dance to the one in + // Sema::ActOnStartCXXMemberDeclarations + const bool DelayTypeCreation = true; + if (GetImportedOrCreateDecl( + D2CXX, D, Importer.getToContext(), D->getTagKind(), DC, + *BeginLocOrErr, Loc, Name.getAsIdentifierInfo(), + cast_or_null(PrevDecl), DelayTypeCreation)) + return D2CXX; + Importer.getToContext().getTypeDeclType( + D2CXX, dyn_cast(DC)); + } else { + if (GetImportedOrCreateDecl(D2CXX, D, Importer.getToContext(), + D->getTagKind(), DC, *BeginLocOrErr, Loc, + Name.getAsIdentifierInfo(), + cast_or_null(PrevDecl))) + return D2CXX; + } - D2 = D2CXX; - D2->setAccess(D->getAccess()); - D2->setLexicalDeclContext(LexicalDC); - if (!DCXX->getDescribedClassTemplate() || DCXX->isImplicit()) - LexicalDC->addDeclInternal(D2); - - if (ClassTemplateDecl *FromDescribed = - DCXX->getDescribedClassTemplate()) { - ClassTemplateDecl *ToDescribed; - if (Error Err = importInto(ToDescribed, FromDescribed)) - return std::move(Err); - D2CXX->setDescribedClassTemplate(ToDescribed); - 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 - // available at record create). - // FIXME: The previous type is cleared but not removed from - // ASTContext's internal storage. - CXXRecordDecl *Injected = nullptr; - for (NamedDecl *Found : D2CXX->noload_lookup(Name)) { - auto *Record = dyn_cast(Found); - if (Record && Record->isInjectedClassName()) { - Injected = Record; - break; - } - } - D2CXX->setTypeForDecl(nullptr); - Importer.getToContext().getInjectedClassNameType(D2CXX, - ToDescribed->getInjectedClassNameSpecialization()); - if (Injected) { - Injected->setTypeForDecl(nullptr); - Importer.getToContext().getTypeDeclType(Injected, D2CXX); + D2 = D2CXX; + D2->setAccess(D->getAccess()); + D2->setLexicalDeclContext(LexicalDC); + if (!DCXX->getDescribedClassTemplate() || DCXX->isImplicit()) + LexicalDC->addDeclInternal(D2); + + const bool IsFriend = D->isInIdentifierNamespace(Decl::IDNS_TagFriend); + if (LexicalDC != DC && IsFriend) { + DC->makeDeclVisibleInContext(D2); + } + + if (ClassTemplateDecl *FromDescribed = + DCXX->getDescribedClassTemplate()) { + ClassTemplateDecl *ToDescribed; + if (Error Err = importInto(ToDescribed, FromDescribed)) + return std::move(Err); + D2CXX->setDescribedClassTemplate(ToDescribed); + 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 + // available at record create). + // FIXME: The previous type is cleared but not removed from + // ASTContext's internal storage. + CXXRecordDecl *Injected = nullptr; + for (NamedDecl *Found : D2CXX->noload_lookup(Name)) { + auto *Record = dyn_cast(Found); + if (Record && Record->isInjectedClassName()) { + Injected = Record; + break; } } - } else if (MemberSpecializationInfo *MemberInfo = + // Create an injected type for the whole redecl chain. + SmallVector Redecls = + getCanonicalForwardRedeclChain(D2CXX); + for (auto *R : Redecls) { + auto *RI = cast(R); + RI->setTypeForDecl(nullptr); + // Below we create a new injected type and assign that to the + // canonical decl, subsequent declarations in the chain will reuse + // that type. + Importer.getToContext().getInjectedClassNameType( + RI, ToDescribed->getInjectedClassNameSpecialization()); + } + // Set the new type for the previous injected decl too. + if (Injected) { + Injected->setTypeForDecl(nullptr); + Importer.getToContext().getTypeDeclType(Injected, D2CXX); + } + } + } else if (MemberSpecializationInfo *MemberInfo = DCXX->getMemberSpecializationInfo()) { TemplateSpecializationKind SK = MemberInfo->getTemplateSpecializationKind(); @@ -2803,27 +2787,24 @@ *POIOrErr); else return POIOrErr.takeError(); - } - - } else { - if (GetImportedOrCreateDecl(D2, D, Importer.getToContext(), - D->getTagKind(), DC, *BeginLocOrErr, Loc, - Name.getAsIdentifierInfo(), PrevDecl)) - return D2; - D2->setLexicalDeclContext(LexicalDC); - LexicalDC->addDeclInternal(D2); } - if (auto QualifierLocOrErr = import(D->getQualifierLoc())) - D2->setQualifierInfo(*QualifierLocOrErr); - else - return QualifierLocOrErr.takeError(); - - if (D->isAnonymousStructOrUnion()) - D2->setAnonymousStructOrUnion(true); + } else { + if (GetImportedOrCreateDecl(D2, D, Importer.getToContext(), + D->getTagKind(), DC, *BeginLocOrErr, Loc, + Name.getAsIdentifierInfo(), PrevDecl)) + return D2; + D2->setLexicalDeclContext(LexicalDC); + LexicalDC->addDeclInternal(D2); } - Importer.MapImported(D, D2); + if (auto QualifierLocOrErr = import(D->getQualifierLoc())) + D2->setQualifierInfo(*QualifierLocOrErr); + else + return QualifierLocOrErr.takeError(); + + if (D->isAnonymousStructOrUnion()) + D2->setAnonymousStructOrUnion(true); if (D->isCompleteDefinition()) if (Error Err = ImportDefinition(D, D2, IDK_Default)) @@ -4979,14 +4960,12 @@ ExpectedDecl 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 + // If this template 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() && !IsFriend) { - if (ExpectedDecl ImportedDefOrErr = import( - Definition->getDescribedClassTemplate())) + ClassTemplateDecl *Definition = getDefinition(D); + if (Definition && Definition != D && !IsFriend) { + if (ExpectedDecl ImportedDefOrErr = import(Definition)) return Importer.MapImported(D, *ImportedDefOrErr); else return ImportedDefOrErr.takeError(); @@ -5002,38 +4981,29 @@ if (ToD) return ToD; + ClassTemplateDecl *FoundByLookup = nullptr; + // We may already have a template of the same name; try to find and match it. if (!DC->isFunctionOrMethod()) { SmallVector ConflictingDecls; SmallVector FoundDecls; DC->getRedeclContext()->localUncachedLookup(Name, FoundDecls); for (auto *FoundDecl : FoundDecls) { - if (!FoundDecl->isInIdentifierNamespace(Decl::IDNS_Ordinary)) + if (!FoundDecl->isInIdentifierNamespace(Decl::IDNS_Ordinary | + Decl::IDNS_TagFriend)) continue; Decl *Found = FoundDecl; - if (auto *FoundTemplate = dyn_cast(Found)) { - - // The class to be imported is a definition. - if (D->isThisDeclarationADefinition()) { - // Lookup will find the fwd decl only if that is more recent than the - // definition. So, try to get the definition if that is available in - // the redecl chain. - ClassTemplateDecl *TemplateWithDef = getDefinition(FoundTemplate); - if (TemplateWithDef) - FoundTemplate = TemplateWithDef; - else - continue; - } + auto *FoundTemplate = dyn_cast(Found); + if (FoundTemplate) { if (IsStructuralMatch(D, FoundTemplate)) { - if (!IsFriend) { - Importer.MapImported(D->getTemplatedDecl(), - FoundTemplate->getTemplatedDecl()); - return Importer.MapImported(D, FoundTemplate); + ClassTemplateDecl* TemplateWithDef = getDefinition(FoundTemplate); + if (D->isThisDeclarationADefinition() && TemplateWithDef) { + return Importer.MapImported(D, TemplateWithDef); } - - continue; + FoundByLookup = FoundTemplate; + break; } } @@ -5070,18 +5040,39 @@ 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); - if (!IsFriend) + + if (D->getDeclContext()->containsDeclAndLoad(D)) + DC->addDeclInternal(D2); + if (DC != LexicalDC && D->getLexicalDeclContext()->containsDeclAndLoad(D)) LexicalDC->addDeclInternal(D2); + if (FoundByLookup) { + auto *Recent = + const_cast(FoundByLookup->getMostRecentDecl()); + + // It is possible that during the import of the class template definition + // we start the import of a fwd friend decl of the very same class template + // and we add the fwd friend decl to the lookup table. But the ToTemplated + // had been created earlier and by that time the lookup could not find + // anything existing, so it has no previous decl. Later, (still during the + // import of the fwd friend decl) we start to import the definition again + // and this time the lookup finds the previous fwd friend class template. + // In this case we must set up the previous decl for the templated decl. + if (!ToTemplated->getPreviousDecl()) { + auto *PrevTemplated = FoundByLookup->getTemplatedDecl()->getMostRecentDecl(); + if (ToTemplated != PrevTemplated) + ToTemplated->setPreviousDecl(PrevTemplated); + } + + D2->setPreviousDecl(Recent); + } + + if (LexicalDC != DC && IsFriend) { + DC->makeDeclVisibleInContext(D2); + } + if (FromTemplated->isCompleteDefinition() && !ToTemplated->isCompleteDefinition()) { // FIXME: Import definition! Index: lib/AST/DeclBase.cpp =================================================================== --- lib/AST/DeclBase.cpp +++ lib/AST/DeclBase.cpp @@ -1466,7 +1466,9 @@ if (Map) { StoredDeclsMap::iterator Pos = Map->find(ND->getDeclName()); assert(Pos != Map->end() && "no lookup entry for decl"); - if (Pos->second.getAsVector() || Pos->second.getAsDecl() == ND) + // Remove the decl only if it is contained. + if ((Pos->second.getAsVector() && Pos->second.containsInVector(ND)) || + Pos->second.getAsDecl() == ND) Pos->second.remove(ND); } } while (DC->isTransparentContext() && (DC = DC->getParent())); Index: lib/ASTMatchers/ASTMatchersInternal.cpp =================================================================== --- lib/ASTMatchers/ASTMatchersInternal.cpp +++ lib/ASTMatchers/ASTMatchersInternal.cpp @@ -606,6 +606,8 @@ cxxConversionDecl; const internal::VariadicDynCastAllOfMatcher varDecl; const internal::VariadicDynCastAllOfMatcher fieldDecl; +const internal::VariadicDynCastAllOfMatcher + indirectFieldDecl; const internal::VariadicDynCastAllOfMatcher functionDecl; const internal::VariadicDynCastAllOfMatcher functionTemplateDecl; Index: unittests/AST/ASTImporterTest.cpp =================================================================== --- unittests/AST/ASTImporterTest.cpp +++ unittests/AST/ASTImporterTest.cpp @@ -11,9 +11,10 @@ // //===----------------------------------------------------------------------===// +#include "clang/AST/ASTImporter.h" #include "MatchVerifier.h" #include "clang/AST/ASTContext.h" -#include "clang/AST/ASTImporter.h" +#include "clang/AST/DeclContextInternals.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Tooling/Tooling.h" @@ -1808,6 +1809,65 @@ EXPECT_NE(To0->getCanonicalDecl(), To1->getCanonicalDecl()); } +TEST_P(ASTImporterTestBase, AnonymousRecords) { + auto *Code = + R"( + struct X { + struct { int a; }; + struct { int b; }; + }; + )"; + Decl *FromTU0 = getTuDecl(Code, Lang_C, "input0.c"); + + Decl *FromTU1 = getTuDecl(Code, Lang_C, "input1.c"); + + auto *X0 = + FirstDeclMatcher().match(FromTU0, recordDecl(hasName("X"))); + auto *X1 = + FirstDeclMatcher().match(FromTU1, recordDecl(hasName("X"))); + Import(X0, Lang_C); + Import(X1, Lang_C); + + auto *ToTU = ToAST->getASTContext().getTranslationUnitDecl(); + // We expect no (ODR) warning during the import. + EXPECT_EQ(0u, ToTU->getASTContext().getDiagnostics().getNumWarnings()); + EXPECT_EQ(1u, + DeclCounter().match(ToTU, recordDecl(hasName("X")))); +} + +TEST_P(ASTImporterTestBase, AnonymousRecordsReversed) { + Decl *FromTU0 = getTuDecl( + R"( + struct X { + struct { int a; }; + struct { int b; }; + }; + )", + Lang_C, "input0.c"); + + Decl *FromTU1 = getTuDecl( + R"( + struct X { // reversed order + struct { int b; }; + struct { int a; }; + }; + )", + Lang_C, "input1.c"); + + auto *X0 = + FirstDeclMatcher().match(FromTU0, recordDecl(hasName("X"))); + auto *X1 = + FirstDeclMatcher().match(FromTU1, recordDecl(hasName("X"))); + Import(X0, Lang_C); + Import(X1, Lang_C); + + auto *ToTU = ToAST->getASTContext().getTranslationUnitDecl(); + // We expect one (ODR) warning during the import. + EXPECT_EQ(1u, ToTU->getASTContext().getDiagnostics().getNumWarnings()); + EXPECT_EQ(2u, + DeclCounter().match(ToTU, recordDecl(hasName("X")))); +} + TEST_P(ASTImporterTestBase, ImportDoesUpdateUsedFlag) { auto Pattern = varDecl(hasName("x")); VarDecl *Imported1; @@ -2930,93 +2990,6 @@ 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)); - - EXPECT_TRUE(ToClass); - EXPECT_TRUE(ToFriendClass); - EXPECT_NE(ToClass, ToFriendClass); - EXPECT_EQ(ToFriendClass->getDefinition(), ToClass); - EXPECT_EQ(ToFriendClass->getPreviousDecl(), ToClass); - EXPECT_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); -} - TEST_P(ASTImporterTestBase, MergeFieldDeclsOfClassTemplateSpecialization) { std::string ClassTemplate = R"( @@ -3381,6 +3354,542 @@ EXPECT_TRUE(ImportedD->getDefinition()); } +struct ImportClasses : ASTImporterTestBase {}; + +TEST_P(ImportClasses, + PrototypeShouldBeImportedAsAPrototypeWhenThereIsNoDefinition) { + Decl *FromTU = getTuDecl("class X;", Lang_CXX); + auto Pattern = cxxRecordDecl(hasName("X"), unless(isImplicit())); + auto FromD = FirstDeclMatcher().match(FromTU, Pattern); + + Decl *ImportedD = Import(FromD, Lang_CXX); + Decl *ToTU = ImportedD->getTranslationUnitDecl(); + + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), 1u); + auto ToD = LastDeclMatcher().match(ToTU, Pattern); + EXPECT_TRUE(ImportedD == ToD); + EXPECT_FALSE(ToD->isThisDeclarationADefinition()); +} + +TEST_P(ImportClasses, ImportPrototypeAfterImportedPrototype) { + Decl *FromTU = getTuDecl("class X; class X;", Lang_CXX); + auto Pattern = cxxRecordDecl(hasName("X"), unless(isImplicit())); + auto From0 = FirstDeclMatcher().match(FromTU, Pattern); + auto From1 = LastDeclMatcher().match(FromTU, Pattern); + + Decl *Imported0 = Import(From0, Lang_CXX); + Decl *Imported1 = Import(From1, Lang_CXX); + Decl *ToTU = Imported0->getTranslationUnitDecl(); + + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), 2u); + auto To0 = FirstDeclMatcher().match(ToTU, Pattern); + auto To1 = LastDeclMatcher().match(ToTU, Pattern); + EXPECT_TRUE(Imported0 == To0); + EXPECT_TRUE(Imported1 == To1); + EXPECT_FALSE(To0->isThisDeclarationADefinition()); + EXPECT_FALSE(To1->isThisDeclarationADefinition()); + EXPECT_EQ(To1->getPreviousDecl(), To0); +} + +TEST_P(ImportClasses, DefinitionShouldBeImportedAsADefinition) { + Decl *FromTU = getTuDecl("class X {};", Lang_CXX); + auto Pattern = cxxRecordDecl(hasName("X"), unless(isImplicit())); + auto *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)->isThisDeclarationADefinition()); +} + +TEST_P(ImportClasses, ImportPrototypeFromDifferentTUAfterImportedPrototype) { + Decl *FromTU0 = getTuDecl("class X;", Lang_CXX, "input0.cc"); + Decl *FromTU1 = getTuDecl("class X;", Lang_CXX, "input1.cc"); + auto Pattern = cxxRecordDecl(hasName("X"), unless(isImplicit())); + auto From0 = FirstDeclMatcher().match(FromTU0, Pattern); + auto From1 = FirstDeclMatcher().match(FromTU1, Pattern); + + Decl *Imported0 = Import(From0, Lang_CXX); + Decl *Imported1 = Import(From1, Lang_CXX); + Decl *ToTU = Imported0->getTranslationUnitDecl(); + + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), 2u); + auto To0 = FirstDeclMatcher().match(ToTU, Pattern); + auto To1 = LastDeclMatcher().match(ToTU, Pattern); + EXPECT_TRUE(Imported0 == To0); + EXPECT_TRUE(Imported1 == To1); + EXPECT_FALSE(To0->isThisDeclarationADefinition()); + EXPECT_FALSE(To1->isThisDeclarationADefinition()); + EXPECT_EQ(To1->getPreviousDecl(), To0); +} + +TEST_P(ImportClasses, ImportDefinitions) { + Decl *FromTU0 = getTuDecl("class X {};", Lang_CXX, "input0.cc"); + Decl *FromTU1 = getTuDecl("class X {};", Lang_CXX, "input1.cc"); + auto Pattern = cxxRecordDecl(hasName("X"), unless(isImplicit())); + auto From0 = FirstDeclMatcher().match(FromTU0, Pattern); + auto From1 = FirstDeclMatcher().match(FromTU1, Pattern); + + Decl *Imported0 = Import(From0, Lang_CXX); + Decl *Imported1 = Import(From1, Lang_CXX); + Decl *ToTU = Imported0->getTranslationUnitDecl(); + + EXPECT_EQ(Imported0, Imported1); + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), 1u); + auto To0 = FirstDeclMatcher().match(ToTU, Pattern); + EXPECT_TRUE(Imported0 == To0); + EXPECT_TRUE(To0->isThisDeclarationADefinition()); +} + +TEST_P(ImportClasses, ImportDefinitionThenPrototype) { + Decl *FromTU0 = getTuDecl("class X {};", Lang_CXX, "input0.cc"); + Decl *FromTU1 = getTuDecl("class X;", Lang_CXX, "input1.cc"); + auto Pattern = cxxRecordDecl(hasName("X"), unless(isImplicit())); + auto FromDef = FirstDeclMatcher().match(FromTU0, Pattern); + auto FromProto = FirstDeclMatcher().match(FromTU1, Pattern); + + Decl *ImportedDef = Import(FromDef, Lang_CXX); + Decl *ImportedProto = Import(FromProto, Lang_CXX); + Decl *ToTU = ImportedDef->getTranslationUnitDecl(); + + EXPECT_NE(ImportedDef, ImportedProto); + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), 2u); + auto ToDef = FirstDeclMatcher().match(ToTU, Pattern); + auto ToProto = LastDeclMatcher().match(ToTU, Pattern); + EXPECT_TRUE(ImportedDef == ToDef); + EXPECT_TRUE(ImportedProto == ToProto); + EXPECT_TRUE(ToDef->isThisDeclarationADefinition()); + EXPECT_FALSE(ToProto->isThisDeclarationADefinition()); + EXPECT_EQ(ToProto->getPreviousDecl(), ToDef); +} + +TEST_P(ImportClasses, ImportPrototypeThenDefinition) { + Decl *FromTU0 = getTuDecl("class X;", Lang_CXX, "input0.cc"); + Decl *FromTU1 = getTuDecl("class X {};", Lang_CXX, "input1.cc"); + auto Pattern = cxxRecordDecl(hasName("X"), unless(isImplicit())); + auto FromProto = FirstDeclMatcher().match(FromTU0, Pattern); + auto FromDef = FirstDeclMatcher().match(FromTU1, Pattern); + + Decl *ImportedProto = Import(FromProto, Lang_CXX); + Decl *ImportedDef = Import(FromDef, Lang_CXX); + Decl *ToTU = ImportedDef->getTranslationUnitDecl(); + + EXPECT_NE(ImportedDef, ImportedProto); + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), 2u); + auto ToProto = FirstDeclMatcher().match(ToTU, Pattern); + auto ToDef = LastDeclMatcher().match(ToTU, Pattern); + EXPECT_TRUE(ImportedDef == ToDef); + EXPECT_TRUE(ImportedProto == ToProto); + EXPECT_TRUE(ToDef->isThisDeclarationADefinition()); + EXPECT_FALSE(ToProto->isThisDeclarationADefinition()); + EXPECT_EQ(ToDef->getPreviousDecl(), ToProto); +} + +struct ImportClassTemplates : ASTImporterTestBase {}; + +TEST_P(ImportClassTemplates, + PrototypeShouldBeImportedAsAPrototypeWhenThereIsNoDefinition) { + Decl *FromTU = getTuDecl("template class X;", Lang_CXX); + auto Pattern = classTemplateDecl(hasName("X"), unless(isImplicit())); + auto FromD = FirstDeclMatcher().match(FromTU, Pattern); + + Decl *ImportedD = Import(FromD, Lang_CXX); + Decl *ToTU = ImportedD->getTranslationUnitDecl(); + + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), 1u); + auto ToD = LastDeclMatcher().match(ToTU, Pattern); + EXPECT_TRUE(ImportedD == ToD); + ASSERT_TRUE(ToD->getTemplatedDecl()); + EXPECT_FALSE(ToD->isThisDeclarationADefinition()); +} + +TEST_P(ImportClassTemplates, ImportPrototypeAfterImportedPrototype) { + Decl *FromTU = getTuDecl( + "template class X; template class X;", Lang_CXX); + auto Pattern = classTemplateDecl(hasName("X"), unless(isImplicit())); + auto From0 = FirstDeclMatcher().match(FromTU, Pattern); + auto From1 = LastDeclMatcher().match(FromTU, Pattern); + + Decl *Imported0 = Import(From0, Lang_CXX); + Decl *Imported1 = Import(From1, Lang_CXX); + Decl *ToTU = Imported0->getTranslationUnitDecl(); + + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), 2u); + auto To0 = FirstDeclMatcher().match(ToTU, Pattern); + auto To1 = LastDeclMatcher().match(ToTU, Pattern); + EXPECT_TRUE(Imported0 == To0); + EXPECT_TRUE(Imported1 == To1); + ASSERT_TRUE(To0->getTemplatedDecl()); + ASSERT_TRUE(To1->getTemplatedDecl()); + EXPECT_FALSE(To0->isThisDeclarationADefinition()); + EXPECT_FALSE(To1->isThisDeclarationADefinition()); + EXPECT_EQ(To1->getPreviousDecl(), To0); + EXPECT_EQ(To1->getTemplatedDecl()->getPreviousDecl(), + To0->getTemplatedDecl()); +} + +TEST_P(ImportClassTemplates, DefinitionShouldBeImportedAsADefinition) { + Decl *FromTU = getTuDecl("template class X {};", Lang_CXX); + auto Pattern = classTemplateDecl(hasName("X"), unless(isImplicit())); + auto *FromD = FirstDeclMatcher().match(FromTU, Pattern); + + Decl *ImportedD = Import(FromD, Lang_CXX); + Decl *ToTU = ImportedD->getTranslationUnitDecl(); + + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), 1u); + auto ToD = LastDeclMatcher().match(ToTU, Pattern); + ASSERT_TRUE(ToD->getTemplatedDecl()); + EXPECT_TRUE(ToD->isThisDeclarationADefinition()); +} + +TEST_P(ImportClassTemplates, + ImportPrototypeFromDifferentTUAfterImportedPrototype) { + Decl *FromTU0 = + getTuDecl("template class X;", Lang_CXX, "input0.cc"); + Decl *FromTU1 = + getTuDecl("template class X;", Lang_CXX, "input1.cc"); + auto Pattern = classTemplateDecl(hasName("X"), unless(isImplicit())); + auto From0 = FirstDeclMatcher().match(FromTU0, Pattern); + auto From1 = FirstDeclMatcher().match(FromTU1, Pattern); + + Decl *Imported0 = Import(From0, Lang_CXX); + Decl *Imported1 = Import(From1, Lang_CXX); + Decl *ToTU = Imported0->getTranslationUnitDecl(); + + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), 2u); + auto To0 = FirstDeclMatcher().match(ToTU, Pattern); + auto To1 = LastDeclMatcher().match(ToTU, Pattern); + EXPECT_TRUE(Imported0 == To0); + EXPECT_TRUE(Imported1 == To1); + ASSERT_TRUE(To0->getTemplatedDecl()); + ASSERT_TRUE(To1->getTemplatedDecl()); + EXPECT_FALSE(To0->isThisDeclarationADefinition()); + EXPECT_FALSE(To1->isThisDeclarationADefinition()); + EXPECT_EQ(To1->getPreviousDecl(), To0); + EXPECT_EQ(To1->getTemplatedDecl()->getPreviousDecl(), + To0->getTemplatedDecl()); +} + +TEST_P(ImportClassTemplates, ImportDefinitions) { + Decl *FromTU0 = + getTuDecl("template class X {};", Lang_CXX, "input0.cc"); + Decl *FromTU1 = + getTuDecl("template class X {};", Lang_CXX, "input1.cc"); + auto Pattern = classTemplateDecl(hasName("X"), unless(isImplicit())); + auto From0 = FirstDeclMatcher().match(FromTU0, Pattern); + auto From1 = FirstDeclMatcher().match(FromTU1, Pattern); + + Decl *Imported0 = Import(From0, Lang_CXX); + Decl *Imported1 = Import(From1, Lang_CXX); + Decl *ToTU = Imported0->getTranslationUnitDecl(); + + EXPECT_EQ(Imported0, Imported1); + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), 1u); + auto To0 = FirstDeclMatcher().match(ToTU, Pattern); + EXPECT_TRUE(Imported0 == To0); + ASSERT_TRUE(To0->getTemplatedDecl()); + EXPECT_TRUE(To0->isThisDeclarationADefinition()); +} + +TEST_P(ImportClassTemplates, ImportDefinitionThenPrototype) { + Decl *FromTU0 = + getTuDecl("template class X {};", Lang_CXX, "input0.cc"); + Decl *FromTU1 = + getTuDecl("template class X;", Lang_CXX, "input1.cc"); + auto Pattern = classTemplateDecl(hasName("X"), unless(isImplicit())); + auto FromDef = FirstDeclMatcher().match(FromTU0, Pattern); + auto FromProto = + FirstDeclMatcher().match(FromTU1, Pattern); + + Decl *ImportedDef = Import(FromDef, Lang_CXX); + Decl *ImportedProto = Import(FromProto, Lang_CXX); + Decl *ToTU = ImportedDef->getTranslationUnitDecl(); + + EXPECT_NE(ImportedDef, ImportedProto); + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), 2u); + auto ToDef = FirstDeclMatcher().match(ToTU, Pattern); + auto ToProto = LastDeclMatcher().match(ToTU, Pattern); + EXPECT_TRUE(ImportedDef == ToDef); + EXPECT_TRUE(ImportedProto == ToProto); + ASSERT_TRUE(ToDef->getTemplatedDecl()); + ASSERT_TRUE(ToProto->getTemplatedDecl()); + EXPECT_TRUE(ToDef->isThisDeclarationADefinition()); + EXPECT_FALSE(ToProto->isThisDeclarationADefinition()); + EXPECT_EQ(ToProto->getPreviousDecl(), ToDef); + EXPECT_EQ(ToProto->getTemplatedDecl()->getPreviousDecl(), + ToDef->getTemplatedDecl()); +} + +TEST_P(ImportClassTemplates, ImportPrototypeThenDefinition) { + Decl *FromTU0 = + getTuDecl("template class X;", Lang_CXX, "input0.cc"); + Decl *FromTU1 = + getTuDecl("template class X {};", Lang_CXX, "input1.cc"); + auto Pattern = classTemplateDecl(hasName("X"), unless(isImplicit())); + auto FromProto = + FirstDeclMatcher().match(FromTU0, Pattern); + auto FromDef = FirstDeclMatcher().match(FromTU1, Pattern); + + Decl *ImportedProto = Import(FromProto, Lang_CXX); + Decl *ImportedDef = Import(FromDef, Lang_CXX); + Decl *ToTU = ImportedDef->getTranslationUnitDecl(); + + EXPECT_NE(ImportedDef, ImportedProto); + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), 2u); + auto ToProto = FirstDeclMatcher().match(ToTU, Pattern); + auto ToDef = LastDeclMatcher().match(ToTU, Pattern); + EXPECT_TRUE(ImportedDef == ToDef); + EXPECT_TRUE(ImportedProto == ToProto); + ASSERT_TRUE(ToProto->getTemplatedDecl()); + ASSERT_TRUE(ToDef->getTemplatedDecl()); + EXPECT_TRUE(ToDef->isThisDeclarationADefinition()); + EXPECT_FALSE(ToProto->isThisDeclarationADefinition()); + EXPECT_EQ(ToDef->getPreviousDecl(), ToProto); + EXPECT_EQ(ToDef->getTemplatedDecl()->getPreviousDecl(), + ToProto->getTemplatedDecl()); +} + +struct ImportFriendClasses : ASTImporterTestBase {}; + +TEST_P(ImportFriendClasses, 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)); + + EXPECT_TRUE(ToClass); + EXPECT_TRUE(ToFriendClass); + EXPECT_NE(ToClass, ToFriendClass); + EXPECT_EQ(ToFriendClass->getDefinition(), ToClass); + EXPECT_EQ(ToFriendClass->getPreviousDecl(), ToClass); + EXPECT_EQ(ToFriendClass->getDescribedClassTemplate()->getPreviousDecl(), + ToClass->getDescribedClassTemplate()); +} + +TEST_P(ImportFriendClasses, 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(has(friendDecl())); + ASSERT_TRUE(MatchVerifier{}.match(FromD, Pattern)); + EXPECT_TRUE(MatchVerifier{}.match(ToD, Pattern)); +} + +TEST_P(ImportFriendClasses, ImportOfRecursiveFriendClassTemplate) { + Decl *FromTu = getTuDecl( + R"( + template class declToImport { + template friend class declToImport; + }; + )", + Lang_CXX, "input.cc"); + + auto *FromD = + FirstDeclMatcher().match(FromTu, classTemplateDecl()); + 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); +} + +TEST_P(ImportFriendClasses, ProperPrevDeclForClassTemplateDecls) { + auto Pattern = classTemplateSpecializationDecl(hasName("X")); + + ClassTemplateSpecializationDecl *Imported1; + { + Decl *FromTU = getTuDecl("template class X;" + "struct Y { friend class X; };", + Lang_CXX, "input0.cc"); + auto *FromD = FirstDeclMatcher().match( + FromTU, Pattern); + + Imported1 = cast(Import(FromD, Lang_CXX)); + } + ClassTemplateSpecializationDecl *Imported2; + { + Decl *FromTU = getTuDecl("template class X;" + "template<> class X{};" + "struct Z { friend class X; };", + Lang_CXX, "input1.cc"); + auto *FromD = FirstDeclMatcher().match( + FromTU, Pattern); + + Imported2 = cast(Import(FromD, Lang_CXX)); + } + + Decl *ToTU = ToAST->getASTContext().getTranslationUnitDecl(); + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), + 2u); + ASSERT_TRUE(Imported2->getPreviousDecl()); + EXPECT_EQ(Imported2->getPreviousDecl(), Imported1); +} + +TEST_P(ImportFriendClasses, TypeForDeclShouldBeSetInTemplated) { + Decl *FromTU0 = getTuDecl( + R"( + class X { + class Y; + }; + class X::Y { + template + friend class F; // The decl context of F is the global namespace. + }; + )", + Lang_CXX, "input0.cc"); + auto *Fwd = FirstDeclMatcher().match( + FromTU0, classTemplateDecl(hasName("F"))); + auto *Imported0 = cast(Import(Fwd, Lang_CXX)); + Decl *FromTU1 = getTuDecl( + R"( + template + class F {}; + )", + Lang_CXX, "input1.cc"); + auto *Definition = FirstDeclMatcher().match( + FromTU1, classTemplateDecl(hasName("F"))); + auto *Imported1 = cast(Import(Definition, Lang_CXX)); + EXPECT_EQ(Imported0->getTemplatedDecl()->getTypeForDecl(), + Imported1->getTemplatedDecl()->getTypeForDecl()); +} + +TEST_P(ImportFriendClasses, DeclsFromFriendsShouldBeInRedeclChains2) { + Decl *From, *To; + std::tie(From, To) = + getImportedDecl("class declToImport {};", Lang_CXX, + "class Y { friend class declToImport; };", Lang_CXX); + auto *Imported = cast(To); + + EXPECT_TRUE(Imported->getPreviousDecl()); +} + +TEST_P(ImportFriendClasses, + ImportOfClassTemplateDefinitionShouldConnectToFwdFriend) { + Decl *ToTU = getToTuDecl( + R"( + class X { + class Y; + }; + class X::Y { + template + friend class F; // The decl context of F is the global namespace. + }; + )", + Lang_CXX); + auto *ToDecl = FirstDeclMatcher().match( + ToTU, classTemplateDecl(hasName("F"))); + Decl *FromTU = getTuDecl( + R"( + template + class F {}; + )", + Lang_CXX, "input0.cc"); + auto *Definition = FirstDeclMatcher().match( + FromTU, classTemplateDecl(hasName("F"))); + auto *ImportedDef = cast(Import(Definition, Lang_CXX)); + EXPECT_TRUE(ImportedDef->getPreviousDecl()); + EXPECT_EQ(ToDecl, ImportedDef->getPreviousDecl()); + EXPECT_EQ(ToDecl->getTemplatedDecl(), + ImportedDef->getTemplatedDecl()->getPreviousDecl()); +} + +TEST_P(ImportFriendClasses, + ImportOfClassTemplateDefinitionAndFwdFriendShouldBeLinked) { + Decl *FromTU0 = getTuDecl( + R"( + class X { + class Y; + }; + class X::Y { + template + friend class F; // The decl context of F is the global namespace. + }; + )", + Lang_CXX, "input0.cc"); + auto *Fwd = FirstDeclMatcher().match( + FromTU0, classTemplateDecl(hasName("F"))); + auto *ImportedFwd = cast(Import(Fwd, Lang_CXX)); + Decl *FromTU1 = getTuDecl( + R"( + template + class F {}; + )", + Lang_CXX, "input1.cc"); + auto *Definition = FirstDeclMatcher().match( + FromTU1, classTemplateDecl(hasName("F"))); + auto *ImportedDef = cast(Import(Definition, Lang_CXX)); + EXPECT_TRUE(ImportedDef->getPreviousDecl()); + EXPECT_EQ(ImportedFwd, ImportedDef->getPreviousDecl()); + EXPECT_EQ(ImportedFwd->getTemplatedDecl(), + ImportedDef->getTemplatedDecl()->getPreviousDecl()); +} + +TEST_P(ImportFriendClasses, ImportOfClassDefinitionAndFwdFriendShouldBeLinked) { + Decl *FromTU0 = getTuDecl( + R"( + class X { + class Y; + }; + class X::Y { + friend class F; // The decl context of F is the global namespace. + }; + )", + Lang_CXX, "input0.cc"); + auto *Friend = FirstDeclMatcher().match(FromTU0, friendDecl()); + QualType FT = Friend->getFriendType()->getType(); + FT = FromTU0->getASTContext().getCanonicalType(FT); + auto *Fwd = cast(FT)->getDecl(); + auto *ImportedFwd = Import(Fwd, Lang_CXX); + Decl *FromTU1 = getTuDecl( + R"( + class F {}; + )", + Lang_CXX, "input1.cc"); + auto *Definition = FirstDeclMatcher().match( + FromTU1, cxxRecordDecl(hasName("F"))); + auto *ImportedDef = Import(Definition, Lang_CXX); + EXPECT_TRUE(ImportedDef->getPreviousDecl()); + EXPECT_EQ(ImportedFwd, ImportedDef->getPreviousDecl()); +} + struct DeclContextTest : ASTImporterTestBase {}; TEST_P(DeclContextTest, removeDeclOfClassTemplateSpecialization) { @@ -3410,6 +3919,40 @@ EXPECT_FALSE(NS->containsDecl(Spec)); } +TEST_P(DeclContextTest, + removeDeclShouldNotFailEvenIfWeHaveExternalVisibleStorage) { + Decl *TU = getTuDecl("extern int A; int A;", Lang_CXX); + auto *A0 = FirstDeclMatcher().match(TU, varDecl(hasName("A"))); + auto *A1 = LastDeclMatcher().match(TU, varDecl(hasName("A"))); + + // Investigate the list. + auto *DC = A0->getDeclContext(); + ASSERT_TRUE(DC->containsDecl(A0)); + ASSERT_TRUE(DC->containsDecl(A1)); + + // Investigate the lookup table. + auto *Map = DC->getLookupPtr(); + ASSERT_TRUE(Map); + auto I = Map->find(A0->getDeclName()); + ASSERT_NE(I, Map->end()); + StoredDeclsList &L = I->second; + // The lookup table contains the most recent decl of A. + ASSERT_NE(L.getAsDecl(), A0); + ASSERT_EQ(L.getAsDecl(), A1); + + ASSERT_TRUE(L.getAsDecl()); + // Simulate the private function DeclContext::reconcileExternalVisibleStorage. + // The point here is to have a Vec with only one element, which is not the + // one we are going to delete from the DC later. + L.setHasExternalDecls(); + ASSERT_TRUE(L.getAsVector()); + ASSERT_EQ(1u, L.getAsVector()->size()); + + // This asserts in the old implementation. + DC->removeDecl(A0); + EXPECT_FALSE(DC->containsDecl(A0)); +} + struct ImportFunctionTemplateSpecializations : ASTImporterTestBase {}; TEST_P(ImportFunctionTemplateSpecializations, @@ -3756,6 +4299,15 @@ INSTANTIATE_TEST_CASE_P(ParameterizedTests, ImportFriendFunctions, DefaultTestValuesForRunOptions, ); +INSTANTIATE_TEST_CASE_P(ParameterizedTests, ImportClasses, + DefaultTestValuesForRunOptions, ); + +INSTANTIATE_TEST_CASE_P(ParameterizedTests, ImportClassTemplates, + DefaultTestValuesForRunOptions, ); + +INSTANTIATE_TEST_CASE_P(ParameterizedTests, ImportFriendClasses, + DefaultTestValuesForRunOptions, ); + INSTANTIATE_TEST_CASE_P(ParameterizedTests, ImportFunctionTemplateSpecializations, DefaultTestValuesForRunOptions, ); Index: unittests/AST/StructuralEquivalenceTest.cpp =================================================================== --- unittests/AST/StructuralEquivalenceTest.cpp +++ unittests/AST/StructuralEquivalenceTest.cpp @@ -597,6 +597,77 @@ EXPECT_FALSE(testStructuralMatch(R0, R1)); } +TEST_F(StructuralEquivalenceRecordTest, AnonymousRecordsShouldBeInequivalent) { + auto t = makeTuDecls( + R"( + struct X { + struct { + int a; + }; + struct { + int b; + }; + }; + )", + "", Lang_C); + auto *TU = get<0>(t); + auto *A = FirstDeclMatcher().match( + TU, indirectFieldDecl(hasName("a"))); + auto *FA = cast(A->chain().front()); + RecordDecl *RA = cast(FA->getType().getTypePtr())->getDecl(); + auto *B = FirstDeclMatcher().match( + TU, indirectFieldDecl(hasName("b"))); + auto *FB = cast(B->chain().front()); + RecordDecl *RB = cast(FB->getType().getTypePtr())->getDecl(); + + ASSERT_NE(RA, RB); + EXPECT_TRUE(testStructuralMatch(RA, RA)); + EXPECT_TRUE(testStructuralMatch(RB, RB)); + EXPECT_FALSE(testStructuralMatch(RA, RB)); +} + +TEST_F(StructuralEquivalenceRecordTest, + RecordsAreInequivalentIfOrderOfAnonRecordsIsDifferent) { + auto t = makeTuDecls( + R"( + struct X { + struct { int a; }; + struct { int b; }; + }; + )", + R"( + struct X { // The order is reversed. + struct { int b; }; + struct { int a; }; + }; + )", + Lang_C); + + auto *TU = get<0>(t); + auto *A = FirstDeclMatcher().match( + TU, indirectFieldDecl(hasName("a"))); + auto *FA = cast(A->chain().front()); + RecordDecl *RA = cast(FA->getType().getTypePtr())->getDecl(); + + auto *TU1 = get<1>(t); + auto *A1 = FirstDeclMatcher().match( + TU1, indirectFieldDecl(hasName("a"))); + auto *FA1 = cast(A1->chain().front()); + RecordDecl *RA1 = cast(FA1->getType().getTypePtr())->getDecl(); + + RecordDecl *X = + FirstDeclMatcher().match(TU, recordDecl(hasName("X"))); + RecordDecl *X1 = + FirstDeclMatcher().match(TU1, recordDecl(hasName("X"))); + ASSERT_NE(X, X1); + EXPECT_FALSE(testStructuralMatch(X, X1)); + + ASSERT_NE(RA, RA1); + EXPECT_TRUE(testStructuralMatch(RA, RA)); + EXPECT_TRUE(testStructuralMatch(RA1, RA1)); + EXPECT_FALSE(testStructuralMatch(RA1, RA)); +} + TEST_F(StructuralEquivalenceRecordTest, UnnamedRecordsShouldBeInequivalentEvenIfTheSecondIsBeingDefined) { auto Code =