Depth of the parameter of friend template class declaration in a template class is 1, while in the specialization the depth is 0. This will cause failure on 'IsStructurallyEquivalent' as a name conflict in 'VisitClassTemplateDecl'(see testcase of 'SkipComparingFriendTemplateDepth'). The patch fix it by ignore the depth only in this special case.
Details
Diff Detail
- Repository
- rG LLVM Github Monorepo
Event Timeline
Could you please elaborate in the commit message what exactly the issue currently is and how the patch addresses it?
The summary tells nothing about what is the real problem to fix here. In the lit test I see that an error message is displayed, this causes the test failure. The problem is that the structural equivalence check is not good for this special case. The imported AST contains a class template specialization for A, in this specialization the friend class template A has a previous decl that points to the original friend class template that is in a ClassTemplateDecl. In the specialization the "depth" of template arguments is 0, but in the original template it is 1 (the "to" code at import contains only the "original template", no specialization). This difference in the "depth" causes the type mismatch when the specialization is imported.
AST dump of this code can show the problem:
template<class T, T U> class A; template<class T, T U> class A { public: template<class P, P Q> friend class A; A(T x):x(x){} private: T x; }; A<int,3> a1(0);
It is really interesting that at friend templates the depth is 1 but friend declarations point to objects outside the class, so really the depth should not increase in a friend template from this point of view. But this is an AST issue.
clang/lib/AST/ASTImporter.cpp | ||
---|---|---|
5857 | It is not good to use ParentMap in the AST importer because it does AST traversal, even worse if this is done on the To context where the AST is modified and may be in incomplete state. | |
clang/unittests/AST/ASTImporterTest.cpp | ||
4252 | I think the namespace __1 is not important for reproduction of this problem. | |
4268 | Functions foo, bar, main are not required. It is only important to have a variable of type A like A<int, 3> a1(0); in the imported code at getTuDecl. | |
4281 | The coding format should be aligned to the format of other test codes in this file, and this is normally same as the clang format guidelines (automatic reformatting does not work in the test code). | |
4318 | Definition is misleading because this is not the definition, it matches the first declaration of A in the AST. Better name is like FromA like in the other tests, or FromXxx. | |
4320 | The imported name can be ToA or ToXxx or ImportedXxx, this makes real distinction between the from and to objects. | |
4322 | This cast is not needed, type of Template is already ClassTemplateDecl*. |
Depth of specialization friend template is 0 while it is 1 in class declaration is reasonable, because after template class specialization there not exists template parameter and the friend template becomes outermost scope, thus depth is 0. Thanks to the different of depth causes the non equivalence of the two 'NonTypeTemplateParm', I think there should ignore comparing the depth in this special case to make two types equivalence.
This fix can cause problems because the depth comparison is omitted all times. It would be better to omit depth check if the imported template is a friend, and has a ClassTemplateSpecializationDecl as context. Probably even then it is possible to construct cases where the checked template has references to other templates with different "depth" which should not omitted in the check. But I have no idea of a better solution, only to pass a ClassTemplateDecl or ClassTemplateSpecializationDecl to StructuralEquivalenceContext and omit the depth check only at this object.
update diff: add more conditions on when to ignore comparing depth of two template classes to minimize influence to others and fix name of test case to make easy understanding
Thanks for your advice. More conditions are added to check when to ignore the comparison and minimize the influence to other.
Also I have tried to not increase depth in friend declaration in the template class, but the code affects others a lot. Only to skip the comparison can pass this special case.
A simple test should be added to StructuralEquivalenceTest.cpp too to check if ignore (and not ignore) depth works.
I think this solution is not always correct, but is still an improvement.
clang/include/clang/AST/ASTStructuralEquivalence.h | ||
---|---|---|
80 | bool IgnoreTemplateParmDepth = false) | |
clang/lib/AST/ASTImporter.cpp | ||
512 | This should be false to have the original behavior if not specified. | |
5857 | Probably add IsFriendTemplate? | |
clang/unittests/AST/ASTImporterTest.cpp | ||
4292 | Probably add hasDefinition() to the matcher. |
clang/lib/AST/ASTImporter.cpp | ||
---|---|---|
5857 | I think this is not needed because D is not always a friend template class according to the testcase (IsFriendTemplate is false in testcase). |
clang/unittests/AST/StructuralEquivalenceTest.cpp | ||
---|---|---|
1700 | IgnoreTemplateParmDepth) { | |
1735 | It should work with more simple code like: makeDecls<ClassTemplateDecl>( R"( template<class> struct A; )", R"( template<class> struct S { template<class> friend struct A; }; )", Lang_CXX03, classTemplateDecl(hasName("A")), classTemplateDecl(hasName("A")) ); |
clang/unittests/AST/StructuralEquivalenceTest.cpp | ||
---|---|---|
1711 | The intent was to have EXPECT_FALSE without ignore depth, and the next test would be not needed. If it does not work with this code a different code can be found. The depth of the (unnamed) template parameter is really different in these cases too, but it is probably not detected by structural equivalence. Maybe template<int U> or template<class T, T U> is needed. But to document this behavior the current test can be added too, with name IgnoreTemplateParmDepthAtTemplateTypeParmDecl, and the new one with name IgnoreTemplateParmDepthAtNonTypeTemplateParmDecl. |
clang/unittests/AST/StructuralEquivalenceTest.cpp | ||
---|---|---|
1711 |
IsStructurallyEquivalent(StructuralEquivalenceContext &Context, TemplateTypeParmDecl *D1, TemplateTypeParmDecl *D2) will be called and return true.
IsStructurallyEquivalent(StructuralEquivalenceContext &Context, QualType T1, QualType T2) is called and return false as the different depth. |
Add testcase to illustrate the difference between TemplateTypeParm and NonTypeTemplateParm in detecting by structural equivalence.
clang/unittests/AST/StructuralEquivalenceTest.cpp | ||
---|---|---|
1718 | The convention is to start variable names with uppercase, for example "Decls". |
As a friend template declaration, compared to the template class definition, the only difference is the depth of the NonTypeTemplateParam. If the template class definition is equivalent to the class in From context, there is no probelm. Otherwise, the friend template declaration is also can't equivalent to it. Because the friend template declaration is equivalent to its definition when ignore the depth. To make a counterexample, we should put the friend declaration in different depth, but they are all equivalent in these cases.
My concern was related to nested namespaces or nested classes with friend declarations that are equivalent and differ only in the nesting level. It may be possible to construct code where a declaration at an inner (nested) level is found to be equivalent with a similar looking class at an outer level. But I now do not have time to look for an example to test it, and I am not fully familiar with exact rules of friend declarations, so I accept this fix.
- The same declcontext will make the template class and friend template class in the same namespace.
- Namespace will not change the depth of template class parameter. So, the equvalence will not be changed whether ignore the depth or not.
- Declcontext of friend tempalte class is outer namespace (if exists), So, classes in this declcontext are equvalent with identical name.
According the the conclusion above, the nested namespace or nested class will not affect the equvalence of the two classes (as far as I concerned).
clang/unittests/AST/StructuralEquivalenceTest.cpp | ||
---|---|---|
140 | See bugprone-argument-comment for details of this format. We use this everywhere we have literals without clear meaning being used as arguments. | |
145 |
clang/unittests/AST/StructuralEquivalenceTest.cpp | ||
---|---|---|
140 | argument comment has been added. |