-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[clang][ASTImporter] Improve import of friend class templates. #74627
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[clang][ASTImporter] Improve import of friend class templates. #74627
Conversation
A friend template that is in a dependent context is not linked into declaration chains (for example with the definition of the befriended template). This condition was not correctly handled by ASTImporter.
@llvm/pr-subscribers-clang Author: Balázs Kéri (balazske) ChangesA friend template that is in a dependent context is not linked into declaration chains (for example with the definition of the befriended template). This condition was not correctly handled by Full diff: https://github.com/llvm/llvm-project/pull/74627.diff 2 Files Affected:
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index f1f335118f37a..a243bf65cca27 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -5919,15 +5919,26 @@ ExpectedDecl ASTNodeImporter::VisitClassTemplateDecl(ClassTemplateDecl *D) {
if (ToD)
return ToD;
- bool IsFriendTemplate = D->getFriendObjectKind() != Decl::FOK_None;
- bool IsDependentContext = DC != LexicalDC ? LexicalDC->isDependentContext()
+ // Should check if a declaration is friend in a dependent context.
+ // Such templates are not linked together in a declaration chain.
+ // The ASTImporter strategy is to map existing forward declarations to
+ // imported ones only if strictly necessary, otherwise import these as new
+ // forward declarations. In case of the "dependent friend" declarations, new
+ // declarations are created, but not linked in a declaration chain.
+ auto IsDependentFriend = [](ClassTemplateDecl *TD) {
+ bool IsFriendTemplate = TD->getFriendObjectKind() != Decl::FOK_None;
+ DeclContext *DC = TD->getDeclContext();
+ DeclContext *LexicalDC = TD->getLexicalDeclContext();
+ bool IsDependentContext = DC != LexicalDC ? LexicalDC->isDependentContext()
: DC->isDependentContext();
- bool DependentFriend = IsFriendTemplate && IsDependentContext;
+ return IsFriendTemplate && IsDependentContext;
+ };
+ bool DependentFriend = IsDependentFriend(D);
ClassTemplateDecl *FoundByLookup = nullptr;
// We may already have a template of the same name; try to find and match it.
- if (!DependentFriend && !DC->isFunctionOrMethod()) {
+ if (!DC->isFunctionOrMethod()) {
SmallVector<NamedDecl *, 4> ConflictingDecls;
auto FoundDecls = Importer.findDeclsInToCtx(DC, Name);
for (auto *FoundDecl : FoundDecls) {
@@ -5943,10 +5954,13 @@ ExpectedDecl ASTNodeImporter::VisitClassTemplateDecl(ClassTemplateDecl *D) {
// FIXME: sufficient conditon for 'IgnoreTemplateParmDepth'?
bool IgnoreTemplateParmDepth =
- FoundTemplate->getFriendObjectKind() != Decl::FOK_None &&
- !D->specializations().empty();
+ (FoundTemplate->getFriendObjectKind() != Decl::FOK_None) !=
+ (D->getFriendObjectKind() != Decl::FOK_None);
if (IsStructuralMatch(D, FoundTemplate, /*Complain=*/true,
IgnoreTemplateParmDepth)) {
+ if (DependentFriend || IsDependentFriend(FoundTemplate))
+ continue;
+
ClassTemplateDecl *TemplateWithDef =
getTemplateDefinition(FoundTemplate);
if (D->isThisDeclarationADefinition() && TemplateWithDef)
diff --git a/clang/unittests/AST/ASTImporterTest.cpp b/clang/unittests/AST/ASTImporterTest.cpp
index 4dd7510bf8ddf..f9ee73626c948 100644
--- a/clang/unittests/AST/ASTImporterTest.cpp
+++ b/clang/unittests/AST/ASTImporterTest.cpp
@@ -4528,6 +4528,168 @@ TEST_P(ImportFriendClasses, ImportOfClassDefinitionAndFwdFriendShouldBeLinked) {
EXPECT_EQ(ImportedFwd, ImportedDef->getPreviousDecl());
}
+TEST_P(ImportFriendClasses,
+ ImportFriendTemplatesInDependentContext_DefToFriend) {
+ Decl *ToTU = getToTuDecl(
+ R"(
+ template<class T1>
+ struct X {
+ template<class T2>
+ friend struct Y;
+ };
+ )",
+ Lang_CXX03);
+ auto *ToYFriend = FirstDeclMatcher<ClassTemplateDecl>().match(
+ ToTU, classTemplateDecl(hasName("Y")));
+ Decl *FromTU = getTuDecl(
+ R"(
+ template<class T1>
+ struct Y {};
+ )",
+ Lang_CXX03, "input0.cc");
+ auto *FromYDef = FirstDeclMatcher<ClassTemplateDecl>().match(
+ FromTU, classTemplateDecl(hasName("Y")));
+ auto *ImportedYDef = Import(FromYDef, Lang_CXX03);
+ EXPECT_TRUE(ImportedYDef);
+ EXPECT_FALSE(ImportedYDef->getPreviousDecl());
+ EXPECT_NE(ImportedYDef, ToYFriend);
+}
+
+TEST_P(ImportFriendClasses,
+ ImportFriendTemplatesInDependentContext_DefToFriend_NE) {
+ Decl *ToTU = getToTuDecl(
+ R"(
+ template<class T1>
+ struct X {
+ template<class T2>
+ friend struct Y;
+ };
+ )",
+ Lang_CXX03);
+ auto *ToYFriend = FirstDeclMatcher<ClassTemplateDecl>().match(
+ ToTU, classTemplateDecl(hasName("Y")));
+ Decl *FromTU = getTuDecl(
+ R"(
+ template<class T1, class T2>
+ struct Y {};
+ )",
+ Lang_CXX03, "input0.cc");
+ auto *FromYDef = FirstDeclMatcher<ClassTemplateDecl>().match(
+ FromTU, classTemplateDecl(hasName("Y")));
+ auto *ImportedYDef = Import(FromYDef, Lang_CXX03);
+ EXPECT_FALSE(ImportedYDef);
+}
+
+TEST_P(ImportFriendClasses,
+ ImportFriendTemplatesInDependentContext_FriendToFriend) {
+ Decl *ToTU = getToTuDecl(
+ R"(
+ template<class T1>
+ struct X {
+ template<class T2>
+ friend struct Y;
+ };
+ )",
+ Lang_CXX03);
+ auto *ToYFriend = FirstDeclMatcher<ClassTemplateDecl>().match(
+ ToTU, classTemplateDecl(hasName("Y")));
+ Decl *FromTU = getTuDecl(
+ R"(
+ template<class T1>
+ struct X {
+ template<class T2>
+ friend struct Y;
+ };
+ )",
+ Lang_CXX03, "input0.cc");
+ auto *FromYFriend = FirstDeclMatcher<ClassTemplateDecl>().match(
+ FromTU, classTemplateDecl(hasName("Y")));
+ auto *ImportedYFriend = Import(FromYFriend, Lang_CXX03);
+ EXPECT_TRUE(ImportedYFriend);
+ EXPECT_FALSE(ImportedYFriend->getPreviousDecl());
+ EXPECT_NE(ImportedYFriend, ToYFriend);
+}
+
+TEST_P(ImportFriendClasses,
+ ImportFriendTemplatesInDependentContext_FriendToFriend_NE) {
+ Decl *ToTU = getToTuDecl(
+ R"(
+ template<class T1>
+ struct X {
+ template<class T2>
+ friend struct Y;
+ };
+ )",
+ Lang_CXX03);
+ auto *ToYFriend = FirstDeclMatcher<ClassTemplateDecl>().match(
+ ToTU, classTemplateDecl(hasName("Y")));
+ Decl *FromTU = getTuDecl(
+ R"(
+ template<class T1>
+ struct X {
+ template<class T2, class T3>
+ friend struct Y;
+ };
+ )",
+ Lang_CXX03, "input0.cc");
+ auto *FromYFriend = FirstDeclMatcher<ClassTemplateDecl>().match(
+ FromTU, classTemplateDecl(hasName("Y")));
+ auto *ImportedYFriend = Import(FromYFriend, Lang_CXX03);
+ EXPECT_FALSE(ImportedYFriend);
+}
+
+TEST_P(ImportFriendClasses,
+ ImportFriendTemplatesInDependentContext_FriendToDef) {
+ Decl *ToTU = getToTuDecl(
+ R"(
+ template<class T1>
+ struct Y {};
+ )",
+ Lang_CXX03);
+ auto *ToYDef = FirstDeclMatcher<ClassTemplateDecl>().match(
+ ToTU, classTemplateDecl(hasName("Y")));
+ Decl *FromTU = getTuDecl(
+ R"(
+ template<class T1>
+ struct X {
+ template<class T2>
+ friend struct Y;
+ };
+ )",
+ Lang_CXX03, "input0.cc");
+ auto *FromYFriend = FirstDeclMatcher<ClassTemplateDecl>().match(
+ FromTU, classTemplateDecl(hasName("Y")));
+ auto *ImportedYFriend = Import(FromYFriend, Lang_CXX03);
+ EXPECT_TRUE(ImportedYFriend);
+ EXPECT_FALSE(ImportedYFriend->getPreviousDecl());
+ EXPECT_NE(ImportedYFriend, ToYDef);
+}
+
+TEST_P(ImportFriendClasses,
+ ImportFriendTemplatesInDependentContext_FriendToDef_NE) {
+ Decl *ToTU = getToTuDecl(
+ R"(
+ template<class T1>
+ struct Y {};
+ )",
+ Lang_CXX03);
+ auto *ToYDef = FirstDeclMatcher<ClassTemplateDecl>().match(
+ ToTU, classTemplateDecl(hasName("Y")));
+ Decl *FromTU = getTuDecl(
+ R"(
+ template<class T1>
+ struct X {
+ template<class T2, class T3>
+ friend struct Y;
+ };
+ )",
+ Lang_CXX03, "input0.cc");
+ auto *FromYFriend = FirstDeclMatcher<ClassTemplateDecl>().match(
+ FromTU, classTemplateDecl(hasName("Y")));
+ auto *ImportedYFriend = Import(FromYFriend, Lang_CXX03);
+ EXPECT_FALSE(ImportedYFriend);
+}
+
TEST_P(ImportFriendClasses, ImportOfRepeatedFriendType) {
const char *Code =
R"(
|
@@ -5943,10 +5954,13 @@ ExpectedDecl ASTNodeImporter::VisitClassTemplateDecl(ClassTemplateDecl *D) { | |||
|
|||
// FIXME: sufficient conditon for 'IgnoreTemplateParmDepth'? | |||
bool IgnoreTemplateParmDepth = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change of this condition was needed to make a failing test ImportOfRecursiveFriendClassTemplateWithNonTypeParm
to pass.
✅ With the latest revision this PR passed the C/C++ code formatter. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally LGTM, except for one suspicious statement.
clang/lib/AST/ASTImporter.cpp
Outdated
DeclContext *DC = TD->getDeclContext(); | ||
DeclContext *LexicalDC = TD->getLexicalDeclContext(); | ||
bool IsDependentContext = DC != LexicalDC ? LexicalDC->isDependentContext() | ||
: DC->isDependentContext(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DeclContext *DC = TD->getDeclContext(); | |
DeclContext *LexicalDC = TD->getLexicalDeclContext(); | |
bool IsDependentContext = DC != LexicalDC ? LexicalDC->isDependentContext() | |
: DC->isDependentContext(); | |
bool IsDependentContext = TD->getLexicalDeclContext()->isDependentContext(); |
The ternary operator (that you inherited from old code) is superfluous, because if DC != LexicalDC
is false, then the two branches are identical.
(By the way, is the logic correct? Perhaps the original author intended to have a difference between the branches...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting observation, I think the simplification can be made in the original code too, I did not find this when I reviewed this code.
…74627) A friend template that is in a dependent context is not linked into declaration chains (for example with the definition of the befriended template). This condition was not correctly handled by `ASTImporter`.
A friend template that is in a dependent context is not linked into declaration chains (for example with the definition of the befriended template). This condition was not correctly handled by
ASTImporter
.