A FriendDecl node can have a friend record type that owns a RecordDecl
object. This object is different than the one got from TypeSourceInfo
object of the FriendDecl. When building a ParentMapContext this owned
tag decaration has to be encountered to have the parent set for it.
Details
Diff Detail
- Repository
- rG LLVM Github Monorepo
Event Timeline
I really do not know why parent of the node for the owned TagDecl node is the FriendDecl and not a TypeLoc node, but it is working.
The code struct A { friend struct Fr; }; caused no problems either (no duplicate visit of struct Fr).
Once the FIXME is removed this looks good, but I was involved in this so better if @sammccall can give the thumbs up at least to the RecursiveASTVisitor code.
clang/include/clang/AST/RecursiveASTVisitor.h | ||
---|---|---|
1577–1578 | I don't think anything is necessary here, because we should never see an OwnedTagDecl here. /// template \<typename T> class A { /// friend class MyVector<T>; // not a friend template /// template \<typename U> friend class B; // not a friend template /// template \<typename U> friend class Foo<T>::Nested; // friend template /// }; So, a FriendTemplateDecl is only created when referencing a nested class template member. But that *must* have been declared before the friend decl, or we will get an error: template<typename T> struct B; template<typename T> struct A { template<typename U> friend struct B<T>::Fr; }; //ERROR: no member named 'Fr' in B<T> So the OwnedTagDecl should always be nullptr here/the issue should never arise in this case. |
This is because the TraverseDecl(OwnedTagDecl) call is performed in the TraverseFriendDecl call, after the TraverseTypeLoc call has already returned -- and I assume the ParentMap just looks uses the stack of traversal calls to generate the parents.
That said, this does raise the question of whether we should instead be changing DEF_TRAVERSE_TYPE(Elaborated) to *always* traverse its owned tag decl; that way the parent would be the TypeLoc node. However in more usual cases an ET's owned tag decl is added to the parent DeclContext; e.g. for struct B {int i} data;, B and data are added as separate declarations in the parent context (which is annoying -- so probably the Type node really *should* always be the parent of that decl, if we were writing the AST from scratch! -- but it is what it is). So if traversed all owned tag decls, we would get some duplicate visitations.
This looks like a reasonable representation of such record decls to me.
Changing the AST to nest them under typelocs instead is indeed a bigger project (and it's hard to say whether it's a good idea without a lot of digging).
clang/include/clang/AST/RecursiveASTVisitor.h | ||
---|---|---|
1577–1578 | That sounds plausible to me, FriendTemplateDecl is extremely obscure and possibly badly designed. | |
clang/unittests/AST/ASTContextParentMapTest.cpp | ||
145 | I'm confused why this is conditional: isn't this the main thing that we're testing? Why do we want the test to silently pass if the AST structure changes so that ownedTagDecl is null? It's hard to tell from reading the code if anything is being tested at all. |
clang/unittests/AST/ASTContextParentMapTest.cpp | ||
---|---|---|
145 | Good point, agree we should expect FrATagDecl to be nonnull. And we should check that FrBTagDecl is null, or at a minimum that FrBTagDecl != FrATagDecl, because if they are the same decl we are traversing it twice. (This could happen if someone decides to try to reuse the ElaboratedType of A's friend decl in B's, to save memory or whatever.) |
I don't think anything is necessary here, because we should never see an OwnedTagDecl here.
In the FriendDecl case the ElaboratedType only has an OwnedTagDecl when struct Fr has not yet been declared before the friend decl.
In the documentation of FriendTemplateDecl on the other hand these examples are given:
So, a FriendTemplateDecl is only created when referencing a nested class template member. But that *must* have been declared before the friend decl, or we will get an error:
So the OwnedTagDecl should always be nullptr here/the issue should never arise in this case.