llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
27.8k stars 11.45k forks source link

RecursiveASTVisitor omits member instantiation ClassTemplatePartialSpecializationDecls #99717

Open smcpeak opened 1 month ago

smcpeak commented 1 month ago

When a ClassTemplatePartialSpecializationDecl is the result of instantiating a member partial specialization, RecursiveASTVisitor (RAV) does not visit it.

Example input:

// test.cc
// Input for rav-misses-ctpsd-memb-inst.cc.

template <class T>
struct Outer {
  template <class U>
  struct Inner;
};

template <class T>
template <class V>
struct Outer<T>::Inner<V*> {
  T t;
  V *u;
};

Outer<int>::Inner<float*> i;

// EOF

First, acronyms:

The directly written syntax corresponds to:

When Outer<T> is instantiated with int, we get:

Then when Outer<int>::Inner<V*> is instantiated with float, we get:

The problem is with CTPSD Outer<int>::Inner<V*>. It is a "member instantiation", created by instantiating Outer<T>::Inner<V*>. RAV fails to traverse it because:

Presumably, the rationale for not traversing the partials is that they normally are directly written, but that isn't the case here.

The problematic CTPSD nodes can be recognized by the fact that getInstantiatedFromMemberTemplate() returns non-null.

As a concrete demonstration, the program in Issue #90586 can be modified by substituting this function:

bool RAVPrinterVisitor::TraverseDecl(clang::Decl *decl)
{
  if (!decl) {
    return true;
  }

  m_os << indentString()
       << decl->getDeclKindName() << "Decl at loc "
       << locStr(decl->getBeginLoc()) << " and addr "
       << (void*)decl
       << "\n";

  ++m_indentLevel;

  // Print the partial specializations of every class template.
  if (auto ctd = dyn_cast<clang::ClassTemplateDecl>(decl)) {

    llvm::SmallVector<clang::ClassTemplatePartialSpecializationDecl *> partials;
    ctd->getPartialSpecializations(partials);

    for (clang::ClassTemplatePartialSpecializationDecl const *pspec : partials) {
      // The missing declarations are those for which `instFromMember`
      // is not null.
      clang::ClassTemplatePartialSpecializationDecl const *instFromMember =
        pspec->getInstantiatedFromMemberTemplate();

      m_os << indentString()
           << "--> Partial specialization: "
           << pspec->Decl::getDeclKindName() << "Decl at loc "
           << locStr(pspec->getBeginLoc()) << " and addr "
           << (void*)pspec << "; instFromMember="
           << (void*)instFromMember
           << "\n";
    }
  }

  // Recurse.
  bool ret = BaseClass::TraverseDecl(decl);

  --m_indentLevel;

  return ret;
}

When the resulting program (which also returns true for shouldVisitTemplateInstantiations() and shouldVisitImplicitCode()) is run on the input above, the key lines of output are:

  ClassTemplateDecl at loc test.cc:4:1 and addr 0x5555cc37beb8
    TemplateTypeParmDecl at loc test.cc:4:11 and addr 0x5555cc37bd58
    CXXRecordDecl at loc test.cc:5:1 and addr 0x5555cc37be28
      CXXRecordDecl at loc test.cc:5:1 and addr 0x5555cc37c128
      ClassTemplateDecl at loc test.cc:6:3 and addr 0x5555cc37c318
        --> Partial specialization: ClassTemplatePartialSpecializationDecl at loc test.cc:12:1 and addr 0x5555cc37c8a8; instFromMember=0
        TemplateTypeParmDecl at loc test.cc:6:13 and addr 0x5555cc37c1b8
        CXXRecordDecl at loc test.cc:7:3 and addr 0x5555cc37c288
    ClassTemplateSpecializationDecl at loc test.cc:4:1 and addr 0x5555cc39bf20
      CXXRecordDecl at loc test.cc:5:1 and addr 0x5555cc39c100
      ClassTemplateDecl at loc test.cc:6:3 and addr 0x5555cc39c2b8
        --> Partial specialization: ClassTemplatePartialSpecializationDecl at loc test.cc:12:1 and addr 0x5555cc39c758; instFromMember=0x5555cc37c8a8

Whereas the CTPSD at 0x5555cc37c8a8 (Outer<T>::Inner<V*>) is traversed by RAV, the one at 0x5555cc39c758 (Outer<int>::Inner<V*>) is not.

Incidentally, ASTNodeTraverser has the same issue, for the same reason, and consequently the CTPSD is also missing from the output of -Xclang -ast-dump.

I don't think this omission is particularly important since the CTPSD of interest is always regarded as a non-definition since its body is never materialized. Nevertheless, it appears to contradict RAV's documented behavior.

A completely untested sketch of a fix would be to insert into RecursiveASTVisitor<Derived>::TraverseTemplateInstantiations:

  llvm::SmallVector<ClassTemplatePartialSpecializationDecl *> partials;
  D->getPartialSpecializations(partials);

  for (ClassTemplatePartialSpecializationDecl const *pspec : partials) {
    // Maybe loop over the redecls here?  Can there be more than one?
    // My guess is no.
    if (pspec->getInstantiatedFromMemberTemplate()) {
      TRY_TO(TraverseDecl(pspec));
    }
  }

but I do not intend to try to fix this myself.

llvmbot commented 1 month ago

@llvm/issue-subscribers-clang-frontend

Author: Scott McPeak (smcpeak)

When a `ClassTemplatePartialSpecializationDecl` is the result of instantiating a member partial specialization, `RecursiveASTVisitor` (RAV) does not visit it. Example input: ```C++ // test.cc // Input for rav-misses-ctpsd-memb-inst.cc. template <class T> struct Outer { template <class U> struct Inner; }; template <class T> template <class V> struct Outer<T>::Inner<V*> { T t; V *u; }; Outer<int>::Inner<float*> i; // EOF ``` First, acronyms: * CTD: ClassTemplateDecl * CTSD: ClassTemplateSpecializationDecl * CTPSD: ClassTemplatePartialSpecializationDecl The directly written syntax corresponds to: * CTD `Outer<T>` * CTD `Outer<T>::Inner<U>` * CTPSD `Outer<T>::Inner<V*>` When `Outer<T>` is instantiated with `int`, we get: * CTSD `Outer<int>` * CTD `Outer<int>::Inner<U>` * CTPSD `Outer<int>::Inner<V*>` Then when `Outer<int>::Inner<V*>` is instantiated with `float`, we get: * CTSD `Outer<int>::Inner<float*>` The problem is with CTPSD `Outer<int>::Inner<V*>`. It is a "member instantiation", created by instantiating `Outer<T>::Inner<V*>`. RAV fails to traverse it because: * It does not correspond to any directly-written syntax, so is not found while iterating over any `DeclContext`. * RAV only looks at concrete specializations in `ClassTemplateDecl`, not partial specializations, which are in a different list. Presumably, the rationale for not traversing the partials is that they normally are directly written, but that isn't the case here. The problematic CTPSD nodes can be recognized by the fact that [getInstantiatedFromMemberTemplate()](https://clang.llvm.org/doxygen/classclang_1_1ClassTemplatePartialSpecializationDecl.html#a234331a7668c3e1ea44dcedbdf4e6ebb) returns non-null. As a concrete demonstration, the program in Issue #90586 can be modified by substituting this function: ```C++ bool RAVPrinterVisitor::TraverseDecl(clang::Decl *decl) { if (!decl) { return true; } m_os << indentString() << decl->getDeclKindName() << "Decl at loc " << locStr(decl->getBeginLoc()) << " and addr " << (void*)decl << "\n"; ++m_indentLevel; // Print the partial specializations of every class template. if (auto ctd = dyn_cast<clang::ClassTemplateDecl>(decl)) { llvm::SmallVector<clang::ClassTemplatePartialSpecializationDecl *> partials; ctd->getPartialSpecializations(partials); for (clang::ClassTemplatePartialSpecializationDecl const *pspec : partials) { // The missing declarations are those for which `instFromMember` // is not null. clang::ClassTemplatePartialSpecializationDecl const *instFromMember = pspec->getInstantiatedFromMemberTemplate(); m_os << indentString() << "--> Partial specialization: " << pspec->Decl::getDeclKindName() << "Decl at loc " << locStr(pspec->getBeginLoc()) << " and addr " << (void*)pspec << "; instFromMember=" << (void*)instFromMember << "\n"; } } // Recurse. bool ret = BaseClass::TraverseDecl(decl); --m_indentLevel; return ret; } ``` When the resulting program (which also returns `true` for `shouldVisitTemplateInstantiations()` and `shouldVisitImplicitCode()`) is run on the input above, the key lines of output are: ```plain-text ClassTemplateDecl at loc test.cc:4:1 and addr 0x5555cc37beb8 TemplateTypeParmDecl at loc test.cc:4:11 and addr 0x5555cc37bd58 CXXRecordDecl at loc test.cc:5:1 and addr 0x5555cc37be28 CXXRecordDecl at loc test.cc:5:1 and addr 0x5555cc37c128 ClassTemplateDecl at loc test.cc:6:3 and addr 0x5555cc37c318 --> Partial specialization: ClassTemplatePartialSpecializationDecl at loc test.cc:12:1 and addr 0x5555cc37c8a8; instFromMember=0 TemplateTypeParmDecl at loc test.cc:6:13 and addr 0x5555cc37c1b8 CXXRecordDecl at loc test.cc:7:3 and addr 0x5555cc37c288 ClassTemplateSpecializationDecl at loc test.cc:4:1 and addr 0x5555cc39bf20 CXXRecordDecl at loc test.cc:5:1 and addr 0x5555cc39c100 ClassTemplateDecl at loc test.cc:6:3 and addr 0x5555cc39c2b8 --> Partial specialization: ClassTemplatePartialSpecializationDecl at loc test.cc:12:1 and addr 0x5555cc39c758; instFromMember=0x5555cc37c8a8 ``` Whereas the CTPSD at 0x5555cc37c8a8 (`Outer<T>::Inner<V*>`) is traversed by RAV, the one at 0x5555cc39c758 (`Outer<int>::Inner<V*>`) is not. Incidentally, `ASTNodeTraverser` has the same issue, for the same reason, and consequently the CTPSD is also missing from the output of `-Xclang -ast-dump`. I don't think this omission is particularly important since the CTPSD of interest is always regarded as a non-definition since its body is never materialized. Nevertheless, it appears to contradict RAV's documented behavior. A completely untested sketch of a fix would be to insert into `RecursiveASTVisitor<Derived>::TraverseTemplateInstantiations`: ```C++ llvm::SmallVector<ClassTemplatePartialSpecializationDecl *> partials; D->getPartialSpecializations(partials); for (ClassTemplatePartialSpecializationDecl const *pspec : partials) { // Maybe loop over the redecls here? Can there be more than one? // My guess is no. if (pspec->getInstantiatedFromMemberTemplate()) { TRY_TO(TraverseDecl(pspec)); } } ``` but I do not intend to try to fix this myself.