Clang tooling namespace issues

Hi,

I’m trying to write a clang compiler tool that will change any functions that take an argument of type StringData by const ref to taking it by value. I would like to have the transformed argument have the same naming format as the original (i.e. if it’s declared without a namespace qualifier, then the rewritten argument should not have one and vice versa). StringData is my string wrapper class.

For example, I want to transform:

void example(const StringData& d);

into:

void example(StringData);

And I also want to be able to transform:

void example_ns(const ns::StringData& d);

into:

void example_ns(ns::StringData);

I’m using RecursiveASTVisitor, and in my VisitFunctionDecl function I check for arguments that match my criteria then use the Rewriter class to change the argument from const ref to a value. My problem is that the transformation always writes the full elaborated type, so for const StringData&, instead of changing the argument type to simply StringData, it changes it to class ns::StringData.

My code currently looks like this:

bool VisitFunctionDecl(FunctionDecl *f) {

for (unsigned int i=0; igetNumParams(); i++) {

ParmVarDecl* p = f->getParamDecl(i);

QualType original_type = p->getOriginalType();

QualType original_nr = original_type.getNonReferenceType();

const IdentifierInfo* id = original_type.getBaseTypeIdentifier();

if (id == NULL) { continue; }

if (!id->getName().compare(“StringData”) &&

original_type->isReferenceType() &&

original_nr.isConstQualified()) {

original_nr.removeLocalConst();

TheRewriter.ReplaceText(p->getSourceRange(), original_nr.getAsString());

}

}

return true;
}

If I run this code on the following example:

void example_ns(const ns::StringData& d);

using namespace ns;
void example(const StringData& d);

The result looks like:

void example_ns(ns::StringData);
using namespace ns;
void example(class ns::StringData);

But I want the result to look like:

void example_ns(ns::StringData);

using namespace ns;

void example(StringData);

Is there a way of finding out if a declaration is made with an explicit namespace qualifier? Is there any way to determine what the actual namespace of a type is? How can I extract the name of the argument type in the same format as it’s declared in the original source?

I am also working on another tool for identifying namespace dependencies so any advice involving namespaces and RecursiveASTVisitors is welcome :slight_smile:

Thank you!

Anna

Hi Anna, if StringData is a C++ class you can call getAsCXXRecordDecl on QualType to get the class declaration. Call getQualifier (defined in TagDecl base class) on the declaration to access the NestedNameSpecifier.

Also, your snippet looks light it could profit from the refactoring libs we have in clang.
A nice intro is:
http://eli.thegreenplace.net/2014/07/29/ast-matchers-and-clang-refactoring-tools

Thank you for getting back to me! Unfortunately, when I call getQualifier() on the CXXRecordDecl it always returns null. I tried calling it for my own class, ns::StringData, but also std::String and both cases return null.

I ran:

QualType original_type = p->getOriginalType();

QualType nonref_type = original_type.getNonReferenceType();

nonref_type.removeLocalConst();

CXXRecordDecl* decl = nonref_type->getAsCXXRecordDecl();

NestedNameSpecifier* name = decl->getQualifier();

if( name == NULL ) {
printf(“null name: %s\n”, nonref_type.getAsString().c_str());
}

on:

void std_ns(const std::string& d);

void strdata_ns(const mongo::StringData& d);

name is always NULL. Any suggestions on why this might be happening?

Thank you!

Thank you for getting back to me! Unfortunately, when I call
getQualifier() on the CXXRecordDecl it always returns null. I tried calling
it for my own class, ns::StringData, but also std::String and both cases
return null.

CXXRecordDecl::getQualifier gives you this:

struct ns::Foo {};
       ^^^^

You want ElaboratedType::getQualifier(), which gives you this:

void f(ns::Foo);
       ^^^^

Thank you for the help!! That fixed my problem :slight_smile:

For anybody interested, what ended up working was:

bool VisitFunctionDecl(FunctionDecl *f) {
for (unsigned int i=0; igetNumParams(); i++) {

ParmVarDecl* p = f->getParamDecl(i);
QualType original_type = p->getOriginalType();
QualType original_nr = original_type.getNonReferenceType();
const IdentifierInfo* id = original_type.getBaseTypeIdentifier();
if (id == NULL) { continue; }

if (!id->getName().compare(“StringData”) &&
original_type->isReferenceType() &&
original_nr.isConstQualified()) {

original_nr.removeLocalConst();

Type::TypeClass t = original_nr->getTypeClass();

if( t == Type::Elaborated ) { // if namespace qualified

const ElaboratedType* etype = cast(orignal_nr);

NestedNameSpecifier* name = etype->getQualifier();

name->print(s, Policy);

}

s << id->getName().data();

TheRewriter.ReplaceText(p->getSourceRange(), s.str());

}

}

return true;
}

Note that this works with arguments declared as “const StringData&” and “const ns::StringData&” but not with something like “const class StringData&”.

Thanks again!