Index: include/clang/ASTMatchers/ASTMatchers.h =================================================================== --- include/clang/ASTMatchers/ASTMatchers.h +++ include/clang/ASTMatchers/ASTMatchers.h @@ -420,6 +420,25 @@ Decl, ClassTemplateSpecializationDecl> classTemplateSpecializationDecl; +/// Matches C++ class template partial specializations. +/// +/// Given +/// \code +/// template +/// class A {}; +/// +/// template +/// class A {}; +/// +/// template<> +/// class A {}; +/// \endcode +/// classTemplatePartialSpecializationDecl() +/// matches the specialization \c A but not \c A +extern const internal::VariadicDynCastAllOfMatcher< + Decl, ClassTemplatePartialSpecializationDecl> + classTemplatePartialSpecializationDecl; + /// Matches declarator declarations (field, variable, function /// and non-type template parameter declarations). /// Index: lib/AST/ASTImporter.cpp =================================================================== --- lib/AST/ASTImporter.cpp +++ lib/AST/ASTImporter.cpp @@ -2877,6 +2877,22 @@ if (Importer.IsStructurallyEquivalent(D->getType(), FoundField->getType())) { Importer.MapImported(D, FoundField); + // In case of a FieldDecl of a ClassTemplateSpecializationDecl, the + // initializer of a FieldDecl might not had been instantiated in the + // "To" context. However, the "From" context might instantiated that, + // thus we have to merge that. + if (Expr *FromInitializer = D->getInClassInitializer()) { + // We don't have yet the initializer set. + if (FoundField->hasInClassInitializer() && + !FoundField->getInClassInitializer()) { + Expr *ToInitializer = Importer.Import(FromInitializer); + if (!ToInitializer) + // We can't return a nullptr here, + // since we already mapped D as imported. + return FoundField; + FoundField->setInClassInitializer(ToInitializer); + } + } return FoundField; } @@ -4518,27 +4534,49 @@ // Try to find an existing specialization with these template arguments. void *InsertPos = nullptr; - ClassTemplateSpecializationDecl *D2 - = ClassTemplate->findSpecialization(TemplateArgs, InsertPos); - if (D2) { - // We already have a class template specialization with these template - // arguments. + ClassTemplateSpecializationDecl *D2 = nullptr; + ClassTemplatePartialSpecializationDecl *PartialSpec = + dyn_cast(D); + if (PartialSpec) + D2 = ClassTemplate->findPartialSpecialization(TemplateArgs, InsertPos); + else + D2 = ClassTemplate->findSpecialization(TemplateArgs, InsertPos); + ClassTemplateSpecializationDecl * const PrevDecl = D2; + RecordDecl *FoundDef = D2 ? D2->getDefinition() : nullptr; + if (FoundDef) { + if (!D->isCompleteDefinition()) { + // The "From" translation unit only had a forward declaration; call it + // the same declaration. + // TODO Handle the redecl chain properly! + return Importer.MapImported(D, FoundDef); + } - // FIXME: Check for specialization vs. instantiation errors. + if (IsStructuralMatch(D, FoundDef)) { - if (RecordDecl *FoundDef = D2->getDefinition()) { - if (!D->isCompleteDefinition() || IsStructuralMatch(D, FoundDef)) { - // The record types structurally match, or the "from" translation - // unit only had a forward declaration anyway; call it the same - // function. - return Importer.MapImported(D, FoundDef); - } + Importer.MapImported(D, FoundDef); + + // Import those those default field initializers which have been + // instantiated in the "From" context, but not in the "To" context. + for (auto *FromField : D->fields()) + Importer.Import(FromField); + + // Import those methods which have been instantiated in the + // "From" context, but not in the "To" context. + for (CXXMethodDecl *FromM : D->methods()) + Importer.Import(FromM); + + // TODO Import instantiated default arguments. + // + // Generally, ASTCommon.h/DeclUpdateKind enum gives a very good hint what + // else could be fused during an AST merge. + + return FoundDef; } - } else { - // Create a new specialization. - if (auto *PartialSpec = - dyn_cast(D)) { - // Import TemplateArgumentListInfo + } else { // We either couldn't find any previous specialization in the "To" + // context, or we found one but without definition. Let's create a + // new specialization and register that at the class template. + if (PartialSpec) { + // Import TemplateArgumentListInfo. TemplateArgumentListInfo ToTAInfo; const auto &ASTTemplateArgs = *PartialSpec->getTemplateArgsAsWritten(); if (ImportTemplateArgumentListInfo(ASTTemplateArgs, ToTAInfo)) @@ -4559,21 +4597,32 @@ D2, D, Importer.getToContext(), D->getTagKind(), DC, StartLoc, IdLoc, ToTPList, ClassTemplate, llvm::makeArrayRef(TemplateArgs.data(), TemplateArgs.size()), - ToTAInfo, CanonInjType, nullptr)) + ToTAInfo, CanonInjType, + cast_or_null(PrevDecl))) return D2; - } else { + // Update InsertPos, because preceding import calls may have invalidated + // it by adding new specializations. + if (!ClassTemplate->findPartialSpecialization(TemplateArgs, InsertPos)) + // Add this partial specialization to the class template. + ClassTemplate->AddPartialSpecialization( + cast(D2), InsertPos); + + } else { // Not a partial specialization. if (GetImportedOrCreateDecl( D2, D, Importer.getToContext(), D->getTagKind(), DC, StartLoc, - IdLoc, ClassTemplate, TemplateArgs, /*PrevDecl=*/nullptr)) + IdLoc, ClassTemplate, TemplateArgs, PrevDecl)) return D2; + + // Update InsertPos, because preceding import calls may have invalidated + // it by adding new specializations. + if (!ClassTemplate->findSpecialization(TemplateArgs, InsertPos)) + // Add this specialization to the class template. + ClassTemplate->AddSpecialization(D2, InsertPos); } D2->setSpecializationKind(D->getSpecializationKind()); - // Add this specialization to the class template. - ClassTemplate->AddSpecialization(D2, InsertPos); - // Import the qualifier, if any. D2->setQualifierInfo(Importer.Import(D->getQualifierLoc())); Index: lib/ASTMatchers/ASTMatchersInternal.cpp =================================================================== --- lib/ASTMatchers/ASTMatchersInternal.cpp +++ lib/ASTMatchers/ASTMatchersInternal.cpp @@ -573,6 +573,9 @@ const internal::VariadicDynCastAllOfMatcher classTemplateSpecializationDecl; +const internal::VariadicDynCastAllOfMatcher< + Decl, ClassTemplatePartialSpecializationDecl> + classTemplatePartialSpecializationDecl; const internal::VariadicDynCastAllOfMatcher declaratorDecl; const internal::VariadicDynCastAllOfMatcher parmVarDecl; Index: unittests/AST/ASTImporterTest.cpp =================================================================== --- unittests/AST/ASTImporterTest.cpp +++ unittests/AST/ASTImporterTest.cpp @@ -2683,6 +2683,263 @@ EXPECT_EQ(FromIndex, 3u); } +TEST_P(ASTImporterTestBase, MergeFieldDeclsOfClassTemplateSpecialization) { + std::string ClassTemplate = + R"( + template + struct X { + int a{0}; // FieldDecl with InitListExpr + X(char) : a(3) {} // (1) + X(int) {} // (2) + }; + )"; + Decl *ToTU = getToTuDecl(ClassTemplate + + R"( + void foo() { + // ClassTemplateSpec with ctor (1): FieldDecl without InitlistExpr + X xc('c'); + } + )", Lang_CXX11); + auto *ToSpec = FirstDeclMatcher().match( + ToTU, classTemplateSpecializationDecl(hasName("X"))); + // FieldDecl without InitlistExpr: + auto *ToField = *ToSpec->field_begin(); + ASSERT_TRUE(ToField); + ASSERT_FALSE(ToField->getInClassInitializer()); + Decl *FromTU = getTuDecl(ClassTemplate + + R"( + void bar() { + // ClassTemplateSpec with ctor (2): FieldDecl WITH InitlistExpr + X xc(1); + } + )", Lang_CXX11); + auto *FromSpec = FirstDeclMatcher().match( + FromTU, classTemplateSpecializationDecl(hasName("X"))); + // FieldDecl with InitlistExpr: + auto *FromField = *FromSpec->field_begin(); + ASSERT_TRUE(FromField); + ASSERT_TRUE(FromField->getInClassInitializer()); + + auto *ImportedSpec = Import(FromSpec, Lang_CXX11); + ASSERT_TRUE(ImportedSpec); + EXPECT_EQ(ImportedSpec, ToSpec); + // After the import, the FieldDecl has to be merged, thus it should have the + // InitListExpr. + EXPECT_TRUE(ToField->getInClassInitializer()); +} + +TEST_P(ASTImporterTestBase, MergeFunctionOfClassTemplateSpecialization) { + std::string ClassTemplate = + R"( + template + struct X { + void f() {} + void g() {} + }; + )"; + Decl *ToTU = getToTuDecl(ClassTemplate + + R"( + void foo() { + X x; + x.f(); + } + )", Lang_CXX11); + Decl *FromTU = getTuDecl(ClassTemplate + + R"( + void bar() { + X x; + x.g(); + } + )", Lang_CXX11); + auto *FromSpec = FirstDeclMatcher().match( + FromTU, classTemplateSpecializationDecl(hasName("X"))); + auto FunPattern = functionDecl(hasName("g"), + hasParent(classTemplateSpecializationDecl())); + auto *FromFun = + FirstDeclMatcher().match(FromTU, FunPattern); + auto *ToFun = + FirstDeclMatcher().match(ToTU, FunPattern); + ASSERT_TRUE(FromFun->hasBody()); + ASSERT_FALSE(ToFun->hasBody()); + auto *ImportedSpec = Import(FromSpec, Lang_CXX11); + ASSERT_TRUE(ImportedSpec); + auto *ToSpec = FirstDeclMatcher().match( + ToTU, classTemplateSpecializationDecl(hasName("X"))); + EXPECT_EQ(ImportedSpec, ToSpec); + EXPECT_TRUE(ToFun->hasBody()); +} + +TEST_P(ASTImporterTestBase, + ODRViolationOfClassTemplateSpecializationsShouldBeReported) { + std::string ClassTemplate = + R"( + template + struct X {}; + )"; + Decl *ToTU = getToTuDecl(ClassTemplate + + R"( + template <> + struct X { + int a; + }; + void foo() { + X x; + } + )", + Lang_CXX11); + Decl *FromTU = getTuDecl(ClassTemplate + + R"( + template <> + struct X { + int b; + }; + void foo() { + X x; + } + )", + Lang_CXX11); + auto *FromSpec = FirstDeclMatcher().match( + FromTU, classTemplateSpecializationDecl(hasName("X"))); + auto *ImportedSpec = Import(FromSpec, Lang_CXX11); + + // We expect one (ODR) warning during the import. + EXPECT_EQ(1u, ToTU->getASTContext().getDiagnostics().getNumWarnings()); + + // The second specialization is different from the first, thus it violates + // ODR, consequently we expect to keep the first specialization only, which is + // already in the "To" context. + EXPECT_TRUE(ImportedSpec); + auto *ToSpec = FirstDeclMatcher().match( + ToTU, classTemplateSpecializationDecl(hasName("X"))); + EXPECT_EQ(ImportedSpec, ToSpec); + EXPECT_EQ(1u, DeclCounter().match( + ToTU, classTemplateSpecializationDecl())); +} + +TEST_P(ASTImporterTestBase, MergeCtorOfClassTemplateSpecialization) { + std::string ClassTemplate = + R"( + template + struct X { + X(char) {} + X(int) {} + }; + )"; + Decl *ToTU = getToTuDecl(ClassTemplate + + R"( + void foo() { + X x('c'); + } + )", Lang_CXX11); + Decl *FromTU = getTuDecl(ClassTemplate + + R"( + void bar() { + X x(1); + } + )", Lang_CXX11); + auto *FromSpec = FirstDeclMatcher().match( + FromTU, classTemplateSpecializationDecl(hasName("X"))); + // Match the void(int) ctor. + auto CtorPattern = + cxxConstructorDecl(hasParameter(0, varDecl(hasType(asString("int")))), + hasParent(classTemplateSpecializationDecl())); + auto *FromCtor = + FirstDeclMatcher().match(FromTU, CtorPattern); + auto *ToCtor = + FirstDeclMatcher().match(ToTU, CtorPattern); + ASSERT_TRUE(FromCtor->hasBody()); + ASSERT_FALSE(ToCtor->hasBody()); + auto *ImportedSpec = Import(FromSpec, Lang_CXX11); + ASSERT_TRUE(ImportedSpec); + auto *ToSpec = FirstDeclMatcher().match( + ToTU, classTemplateSpecializationDecl(hasName("X"))); + EXPECT_EQ(ImportedSpec, ToSpec); + EXPECT_TRUE(ToCtor->hasBody()); +} + +TEST_P(ASTImporterTestBase, + ClassTemplatePartialSpecializationsShouldNotBeDuplicated) { + auto Code = + R"( + // primary template + template + class A {}; + + // partial specialization + template + class A {}; + )"; + Decl *ToTU = getToTuDecl(Code, Lang_CXX11); + Decl *FromTU = getTuDecl(Code, Lang_CXX11); + auto *FromSpec = + FirstDeclMatcher().match( + FromTU, classTemplatePartialSpecializationDecl()); + auto *ToSpec = + FirstDeclMatcher().match( + ToTU, classTemplatePartialSpecializationDecl()); + + auto *ImportedSpec = Import(FromSpec, Lang_CXX11); + EXPECT_EQ(ImportedSpec, ToSpec); + EXPECT_EQ(1u, DeclCounter().match( + ToTU, classTemplatePartialSpecializationDecl())); +} + +TEST_P(ASTImporterTestBase, ClassTemplateSpecializationsShouldNotBeDuplicated) { + auto Code = + R"( + // primary template + template + class A {}; + + // full specialization + template<> + class A {}; + )"; + Decl *ToTU = getToTuDecl(Code, Lang_CXX11); + Decl *FromTU = getTuDecl(Code, Lang_CXX11); + auto *FromSpec = FirstDeclMatcher().match( + FromTU, classTemplateSpecializationDecl()); + auto *ToSpec = FirstDeclMatcher().match( + ToTU, classTemplateSpecializationDecl()); + + auto *ImportedSpec = Import(FromSpec, Lang_CXX11); + EXPECT_EQ(ImportedSpec, ToSpec); + EXPECT_EQ(1u, DeclCounter().match( + ToTU, classTemplateSpecializationDecl())); +} + +TEST_P(ASTImporterTestBase, ClassTemplateFullAndPartialSpecsShouldNotBeMixed) { + std::string PrimaryTemplate = + R"( + template + class A {}; + )"; + auto PartialSpec = + R"( + template + class A {}; + )"; + auto FullSpec = + R"( + template<> + class A {}; + )"; + Decl *ToTU = getToTuDecl(PrimaryTemplate + FullSpec, Lang_CXX11); + Decl *FromTU = getTuDecl(PrimaryTemplate + PartialSpec, Lang_CXX11); + auto *FromSpec = FirstDeclMatcher().match( + FromTU, classTemplateSpecializationDecl()); + + auto *ImportedSpec = Import(FromSpec, Lang_CXX11); + EXPECT_TRUE(ImportedSpec); + // Check the number of partial specializations. + EXPECT_EQ(1u, DeclCounter().match( + ToTU, classTemplatePartialSpecializationDecl())); + // Check the number of full specializations. + EXPECT_EQ(1u, DeclCounter().match( + ToTU, classTemplateSpecializationDecl( + unless(classTemplatePartialSpecializationDecl())))); +} + struct DeclContextTest : ASTImporterTestBase {}; TEST_P(DeclContextTest, removeDeclOfClassTemplateSpecialization) {