[cfe-dev] [AST Matcher API] How to match against a subtree?
Stephen Kelly via cfe-dev
cfe-dev at lists.llvm.org
Wed Jul 25 15:00:26 PDT 2018
Gábor Márton via cfe-dev wrote:
> Hi,
>
> I'd like to do a traversal on a subtree rooted on a specific node, but
> not on the whole tree rooted at the TranslataionUnitDecl. My
> understanding is this is not possible today, or is it? I wonder why
> this was not missing for anybody.
Hi Gabor,
I encountered the same issue, and wrote a similar solution. However, then I
realized the ast_matchers::match documentation says:
/// If you want to find all matches on the sub-tree rooted at \c Node
(rather
/// than only the matches on \c Node itself), surround the \c Matcher with a
/// \c findAll().
So, we should be able to do something like
Decl* someRootDeclContext = ...;
clang::ast_matchers::match(
decl(findAll(callExpr()))
, *someRootDeclContext, *Result.Context);
However, that does not work because findAll is implemented with eachOf and
decl(eachOf(callExpr(), expr()))
doesn't work for the same reason
decl(callExpr())
doesn't work - a decl is never a callExpr.
I filed a bug for this: https://bugs.llvm.org/show_bug.cgi?id=38318
Meanwhile, findAll might work for your specific case, or you might be able
to write a replacement or use something more specific.
A case I encountered involved replacing the type of certain varDecl nodes in
the source code, but porting them if they are passed by reference in a
function call (a case I handled separately).
Given
void takeRef(int&)
{
}
void takeConstRef(int const&)
{
}
void takeVal(int)
{
}
struct IntWrapper
{
IntWrapper(int i) : m_i(i) {}
operator int() { return m_i; }
private:
int m_i;
};
int main()
{
int a = 42;
int b = 7;
takeVal(a);
takeConstRef(a);
takeVal(b);
takeRef(b);
}
the goal is to replace
int a = 42;
with
IntWrapper a = 42;
but leave
int b = 7;
untouched because
takeRef(b);
would not compile if the varDecl is ported.
Here is my solution which uses match(). You should be able to do something
similar for your case without the additional API.
Thanks,
Stephen.
#include "PortToIntwrapperCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
using namespace clang::ast_matchers;
namespace clang {
namespace tidy {
namespace misc {
void PortToIntwrapperCheck::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(
varDecl(
hasType(asString("int")),
unless(parmVarDecl()),
hasDeclContext(decl().bind("varDeclContext"))
).bind("portToIntWrapperIfPossible")
, this);
}
void PortToIntwrapperCheck::check(const MatchFinder::MatchResult
&Result) {
if (const auto *portToIntWrapper =
Result.Nodes.getNodeAs<VarDecl>("portToIntWrapperIfPossible"))
{
const auto *varDeclContext =
Result.Nodes.getNodeAs<Decl>("varDeclContext");
// Got a match for a particular var.
// Check if it is passed by reference in any calls
// within the context it is defined in
auto usesByReference = clang::ast_matchers::match(
decl(forEachDescendant(
callExpr(forEachArgumentWithParam(
declRefExpr(
to(varDecl(equalsNode(portToIntWrapper)))
).bind("arg"),
parmVarDecl(
hasType(lValueReferenceType(
unless(pointee(isConstQualified()))
))
)
))
))
, *varDeclContext, *Result.Context);
if (usesByReference.empty())
{
// All uses are safe to port to IntWrapper
auto tInfo = portToIntWrapper->getTypeSourceInfo();
auto tLoc = tInfo->getTypeLoc();
SourceRange typeRange = tLoc.getSourceRange();
diag(typeRange.getBegin(), "type of %0 should be IntWrapper")
<< portToIntWrapper
<< FixItHint::CreateReplacement(typeRange, "IntWrapper");
}
}
}
} // namespace misc
} // namespace tidy
} // namespace clang
More information about the cfe-dev
mailing list