Jared Hoberock
Jared Hoberock

Reputation: 11406

How to find SourceLocation of the outer template parameter list of a class template member function definition?

I am working on a Clang AST generated from the following source code:

template<class T>
struct my_class
{
  template<class U>
  void foo(U arg);
};

template<class V>
template<class W> void my_class<V>::foo(W arg)
{
}

int main()
{
  return 0;
}

I have a FunctionDecl corresponding to foo's definition at line 9. I want to find the SourceLocation of template<class V> at line 8.

Is this possible?

I am analyzing the AST for the purposes of rewriting it.

Upvotes: 3

Views: 120

Answers (1)

Scott McPeak
Scott McPeak

Reputation: 12749

The "outer" template parameter lists are associated with the DeclaratorDecl, and can be retrieved using the getNumTemplateParameterLists() and getTemplateParameterList(unsigned) methods.

The methods don't have any documentation, but if you look into the Decl.h source code, you can see that these methods access a QualifierInfo structure, which has documentation on the two relevant fields.

Then, once we get the relevant TemplateParameterList, the locations of the template keyword and the angle brackets can be obtained by calling getTemplateLoc(), getLAngleLoc(), and getRAngleLoc(). Locations for the individual parameters are available from the NamedDecl nodes accessible via asArray().

So, given a FunctionDecl *functionDecl, we can print (say) the locations of the outer template keywords like this:

  for (unsigned i=0; i < functionDecl->getNumTemplateParameterLists(); ++i) {
    clang::TemplateParameterList *paramList =
      functionDecl->getTemplateParameterList(i);

    cout << "outer template parameter list " << i << "\n"
         << "  TemplateLoc: "
         << paramList->getTemplateLoc().
              printToString(m_astContext.getSourceManager()) << "\n";
  }

One related quirk/detail: When a DeclaratorDecl (which is a base class of FunctionDecl and CXXMethodDecl) has "outer" template parameters, its getBeginLoc() method will return the location of the template keyword of the first outer. If you want to get the location of the beginning of the declaration after all template parameter lists (both inner and outer, despite the documentation only mentioning outer), use DeclaratorDecl::getInnerLocStart(). For the example in the question, that returns the location of the void keyword (for both declarations of foo).

Complete example

Here is a complete program demonstrating the method:

// outer-tparams.cc
// Report "outer" template parameter lists.

#include "clang/AST/RecursiveASTVisitor.h"                 // clang::RecursiveASTVisitor
#include "clang/Basic/Diagnostic.h"                        // clang::DiagnosticsEngine
#include "clang/Basic/DiagnosticOptions.h"                 // clang::DiagnosticOptions
#include "clang/Frontend/ASTUnit.h"                        // clang::ASTUnit
#include "clang/Frontend/CompilerInstance.h"               // clang::CompilerInstance
#include "clang/Serialization/PCHContainerOperations.h"    // clang::PCHContainerOperations

#include "llvm/Support/raw_ostream.h"                      // llvm::raw_string_ostream

#include <iostream>                                        // std::cout
#include <sstream>                                         // std::ostringstream
#include <string>                                          // std::string

#include <assert.h>                                        // assert

// Convenient string construction.
#define stringb(stuff) \
  (static_cast<std::ostringstream&>(std::ostringstream() << stuff).str())

using clang::dyn_cast;

using std::cout;
using std::endl;
using std::string;


class Visitor : public clang::RecursiveASTVisitor<Visitor> {
public:      // data
  clang::ASTContext &m_astContext;

public:      // methods
  Visitor(clang::ASTUnit *astUnit)
    : m_astContext(astUnit->getASTContext())
  {}

  // Convenience methods to stringify some things.
  string locStr(clang::SourceLocation loc) const;
  string declLocStr(clang::Decl const *decl) const;
  string declNameLocStr(clang::NamedDecl const *decl) const;
  string templateParameterListStr(
    clang::TemplateParameterList const *paramList) const;

  // Visitor methods.
  bool VisitFunctionDecl(clang::FunctionDecl *functionDecl);

  // Kick off the traversal.
  void traverseTU();
};


string Visitor::locStr(clang::SourceLocation loc) const
{
  return loc.printToString(m_astContext.getSourceManager());
}

string Visitor::declLocStr(clang::Decl const *decl) const
{
  return locStr(decl->getLocation());
}

string Visitor::declNameLocStr(clang::NamedDecl const *decl) const
{
  return stringb(decl->getQualifiedNameAsString() <<
                 " declared at " << declLocStr(decl));
}


string Visitor::templateParameterListStr(
  clang::TemplateParameterList const *paramList) const
{
  string ret;
  llvm::raw_string_ostream rso(ret);

  clang::PrintingPolicy printingPolicy(m_astContext.getLangOpts());
  bool const omitTemplateKW = false;
  paramList->print(rso, m_astContext, printingPolicy, omitTemplateKW);

  // Annoyingly, the string has a trailing space.
  ret.erase(ret.find_last_not_of(" \t\n\r\f\v") + 1);

  return ret;
}


bool Visitor::VisitFunctionDecl(clang::FunctionDecl *functionDecl)
{
  cout << "FunctionDecl: " << declNameLocStr(functionDecl) << "\n";

  // Iterate over the "outer" template parameter lists, i.e., those
  // associated with qualifiers in the name of the declared entity, but
  // not directly associated with the entity itself.  (Parameters for
  // the entity itself are on the 'TemplateDecl' if it is a template.)
  for (unsigned i=0; i < functionDecl->getNumTemplateParameterLists(); ++i) {
    clang::TemplateParameterList *paramList =
      functionDecl->getTemplateParameterList(i);

    cout << "  outer template parameter list " << i << "\n"
         << "    syntax: \"" << templateParameterListStr(paramList) << "\"\n"
         << "    TemplateLoc: " << locStr(paramList->getTemplateLoc()) << "\n"
         << "    LAngleLoc: " << locStr(paramList->getLAngleLoc()) << "\n"
         << "    RAngleLoc: " << locStr(paramList->getRAngleLoc()) << "\n"
         ;
  }

  return true;
}


void Visitor::traverseTU()
{
  this->TraverseDecl(m_astContext.getTranslationUnitDecl());
}


int main(int argc, char const **argv)
{
  // Copy the arguments into a vector of char pointers since that is
  // what 'createInvocationFromCommandLine' wants.
  std::vector<char const *> commandLine;
  {
    // Path to the 'clang' binary that I am behaving like.  This path is
    // used to compute the location of compiler headers like stddef.h.
    commandLine.push_back(CLANG_LLVM_INSTALL_DIR "/bin/clang");

    for (int i = 1; i < argc; ++i) {
      commandLine.push_back(argv[i]);
    }
  }

  // Parse the command line options.
  std::shared_ptr<clang::CompilerInvocation> compilerInvocation(
    clang::createInvocation(llvm::ArrayRef(commandLine)));
  if (!compilerInvocation) {
    // Command line parsing errors have already been printed.
    return 2;
  }

  // Boilerplate.
  std::shared_ptr<clang::PCHContainerOperations> pchContainerOps(
    new clang::PCHContainerOperations());
  clang::IntrusiveRefCntPtr<clang::DiagnosticsEngine> diagnosticsEngine(
    clang::CompilerInstance::createDiagnostics(
      &(compilerInvocation->getDiagnosticOpts())));

  // Run the Clang parser to produce an AST.
  std::unique_ptr<clang::ASTUnit> ast(
    clang::ASTUnit::LoadFromCompilerInvocationAction(
      compilerInvocation,
      pchContainerOps,
      diagnosticsEngine));

  if (ast == nullptr ||
      diagnosticsEngine->getNumErrors() > 0) {
    // Error messages have already been printed.
    return 2;
  }

  Visitor visitor(ast.get());
  visitor.traverseTU();

  return 0;
}


// EOF
# Makefile

# Default target.
all:
.PHONY: all


# ---- Configuration ----
# Installation directory from a binary distribution.
CLANG_LLVM_INSTALL_DIR = $(HOME)/opt/clang+llvm-16.0.0-x86_64-linux-gnu-ubuntu-18.04

# ---- llvm-config query results ----
# Program to query the various LLVM configuration options.
LLVM_CONFIG := $(CLANG_LLVM_INSTALL_DIR)/bin/llvm-config

# C++ compiler options to ensure ABI compatibility.
LLVM_CXXFLAGS := $(shell $(LLVM_CONFIG) --cxxflags)

# Directory containing the clang library files, both static and dynamic.
LLVM_LIBDIR := $(shell $(LLVM_CONFIG) --libdir)

# Other flags needed for linking, whether statically or dynamically.
LLVM_LDFLAGS_AND_SYSTEM_LIBS := $(shell $(LLVM_CONFIG) --ldflags --system-libs)


# ---- Compiler options ----
# C++ compiler.
CXX := $(CLANG_LLVM_INSTALL_DIR)/bin/clang++

# Compiler options, including preprocessor options.
CXXFLAGS =
CXXFLAGS += -Wall
CXXFLAGS += -Werror

# Get llvm compilation flags.
CXXFLAGS += $(LLVM_CXXFLAGS)

# Tell the source code where the clang installation directory is.
CXXFLAGS += -DCLANG_LLVM_INSTALL_DIR='"$(CLANG_LLVM_INSTALL_DIR)"'

# Linker options.
LDFLAGS =

# Pull in clang+llvm via libclang-cpp.so, which has everything, but is
# only available as a dynamic library.
LDFLAGS += -lclang-cpp

# Arrange for the compiled binary to search the libdir for that library.
# Otherwise, one can set the LD_LIBRARY_PATH envvar before running it.
# Note: the -rpath switch does not work on Windows.
LDFLAGS += -Wl,-rpath=$(LLVM_LIBDIR)

# It appears that llvm::raw_os_ostream::~raw_os_ostream is missing from
# libclang-cpp, so I have to link with LLVMSupport statically.
LDFLAGS += -lLLVMSupport

# Get the needed -L search path, plus things like -ldl.
LDFLAGS += $(LLVM_LDFLAGS_AND_SYSTEM_LIBS)


# ---- Recipes ----
# Compile a C++ source file.
%.o: %.cc
    $(CXX) -c -o $@ $(CXXFLAGS) $<

# Executable.
all: outer-tparams.exe
outer-tparams.exe: outer-tparams.o
    $(CXX) -g -Wall -o $@ $^ $(LDFLAGS)

# Test.
.PHONY: run
run: outer-tparams.exe
    ./outer-tparams.exe test.cc

.PHONY: clean
clean:
    $(RM) *.o *.exe


# EOF

When run on test.cc, the example in the question:

template<class T>
struct my_class
{
  template<class U>
  void foo(U arg);
};

template<class V>
template<class W> void my_class<V>::foo(W arg)
{
}

int main()
{
  return 0;
}

it prints:

FunctionDecl: my_class::foo declared at test.cc:5:8
FunctionDecl: my_class::foo declared at test.cc:9:37
  outer template parameter list 0
    syntax: "template <class V>"
    TemplateLoc: test.cc:8:1       <-------- what we wanted
    LAngleLoc: test.cc:8:9
    RAngleLoc: test.cc:8:17
FunctionDecl: main declared at test.cc:13:5

When run on this more complicated example:

// test2.cc
// More complicated example.

template <class P1>
class C1 {
  template <class P2>
  class C2 {
    template <class P3>
    class C3 {
      class C4 {
        template <class P5>
        class C5 {
          int regularMethod();

          template <class P6>
          int templatedMethod();
        };
      };
    };
  };
};

template <class P1>
template <class P2>
template <class P3>
template <class P5>     // Last "outer" list.
int C1<P1>::C2<P2>::C3<P3>::C4::C5<P5>::regularMethod()
{
  return 1;
}

template <class P1>
template <class P2>
template <class P3>
template <class P5>     // Last "outer" list.
template <class P6>     // Not an "outer" list.
int C1<P1>::C2<P2>::C3<P3>::C4::C5<P5>::templatedMethod()
{
  return 2;
}

template <>
template <>
template <>
template <>
template <>             // This *is* an "outer" template parameter list.
int C1<int>::C2<int>::C3<int>::C4::C5<int>::templatedMethod<int>()
{
  return 3;
}

// EOF

it prints:

FunctionDecl: C1::C2::C3::C4::C5::regularMethod declared at test2.cc:13:15
FunctionDecl: C1::C2::C3::C4::C5::templatedMethod declared at test2.cc:16:15
FunctionDecl: C1::C2::C3::C4::C5::regularMethod declared at test2.cc:27:41
  outer template parameter list 0
    syntax: "template <class P1>"
    TemplateLoc: test2.cc:23:1
    LAngleLoc: test2.cc:23:10
    RAngleLoc: test2.cc:23:19
  outer template parameter list 1
    syntax: "template <class P2>"
    TemplateLoc: test2.cc:24:1
    LAngleLoc: test2.cc:24:10
    RAngleLoc: test2.cc:24:19
  outer template parameter list 2
    syntax: "template <class P3>"
    TemplateLoc: test2.cc:25:1
    LAngleLoc: test2.cc:25:10
    RAngleLoc: test2.cc:25:19
  outer template parameter list 3
    syntax: "template <class P5>"
    TemplateLoc: test2.cc:26:1
    LAngleLoc: test2.cc:26:10
    RAngleLoc: test2.cc:26:19
FunctionDecl: C1::C2::C3::C4::C5::templatedMethod declared at test2.cc:37:41
  outer template parameter list 0
    syntax: "template <class P1>"
    TemplateLoc: test2.cc:32:1
    LAngleLoc: test2.cc:32:10
    RAngleLoc: test2.cc:32:19
  outer template parameter list 1
    syntax: "template <class P2>"
    TemplateLoc: test2.cc:33:1
    LAngleLoc: test2.cc:33:10
    RAngleLoc: test2.cc:33:19
  outer template parameter list 2
    syntax: "template <class P3>"
    TemplateLoc: test2.cc:34:1
    LAngleLoc: test2.cc:34:10
    RAngleLoc: test2.cc:34:19
  outer template parameter list 3
    syntax: "template <class P5>"
    TemplateLoc: test2.cc:35:1
    LAngleLoc: test2.cc:35:10
    RAngleLoc: test2.cc:35:19
FunctionDecl: C1<int>::C2<int>::C3<int>::C4::C5<int>::templatedMethod declared at test2.cc:47:45
  outer template parameter list 0
    syntax: "template <>"
    TemplateLoc: test2.cc:42:1
    LAngleLoc: test2.cc:42:10
    RAngleLoc: test2.cc:42:11
  outer template parameter list 1
    syntax: "template <>"
    TemplateLoc: test2.cc:43:1
    LAngleLoc: test2.cc:43:10
    RAngleLoc: test2.cc:43:11
  outer template parameter list 2
    syntax: "template <>"
    TemplateLoc: test2.cc:44:1
    LAngleLoc: test2.cc:44:10
    RAngleLoc: test2.cc:44:11
  outer template parameter list 3
    syntax: "template <>"
    TemplateLoc: test2.cc:45:1
    LAngleLoc: test2.cc:45:10
    RAngleLoc: test2.cc:45:11
  outer template parameter list 4
    syntax: "template <>"
    TemplateLoc: test2.cc:46:1
    LAngleLoc: test2.cc:46:10
    RAngleLoc: test2.cc:46:11

Upvotes: 3

Related Questions