[cfe-dev] Qt 4 to Qt 5 porting tool with the tooling branch

Stephen Kelly steveire at gmail.com
Mon Jun 18 04:22:51 PDT 2012


Hi there,

A few weeks ago I was getting started with the tooling branch to do some 
porting from Qt 4 to Qt 5 (I was off topic though :)):

http://thread.gmane.org/gmane.comp.compilers.clang.devel/20433/focus=20575

I wrote a blog post with some results here:

http://www.kdab.com/automated-porting-from-qt-4-to-qt-5/

I also wrote the following to colleagues, and thought I'd get some fact 
checking on it from the people who might know, so that the new API in the 
branch can be documented better:


[Begin copy-paste]

The tool works by using the CMake integration to find out what command 
should be used to compile the file passed to it for porting. It then parses 
that file and runs a visitor pattern over the parsed AST. While visiting, it 
checks to see if the node matches what was specified. When it gets a match 
it invokes the run() method of a callback.

The run() method is the one that sets up a replacement for the matched AST 
node with some text. The text can be the new method being ported to, or some 
other generated string.

The matched nodes can be extracted by their id and type. The type takes a 
while to get used to because there are many of them describing all the 
different parts of C++ syntax.

http://clang.llvm.org/doxygen/classclang_1_1Decl.html

Once we have the replacement text, we add it to the Replacements structure, 
which is processed after matching to to the actual changes.

Knowing how to write a matcher or what type to use to extract a node by id 
can be tricky, but can be made easier by running 'clang -cc1 -ast-dump 
myfile.cpp' to see what types the parser sees.

After that, the matches are actually nested function calls (the 
capitalisation style for functions seems funny to use Qt developers). Nested 
matches are usually specializations.

So

    Expression()

matches any expression. As does

    Expression(Expression())

The inner one is redundant.

    Expression(Function())

matches an expression which is a function, not an expression which contains 
inside it a function.

child relationships are found with has()

    Expression(has(Function()))

finds for example

    foo(bar())

where foo() would be the Expression() and bar() would be the Function().

To use these in the callback, we would write

    Id("expr", Expression(has(Id("func", Function()))))

and then

    const Expr *Outer =
        Result.Nodes.getStmtAs<Expr>("expr");
    const Expr *Inner =
        Result.Nodes.getStmtAs<Expr>("func");


Match expressions next to each other are by default grouped in an AllOf 
expression.

So

    Method(
      HasName("dataChanged"),
      OfClass(
        IsDerivedFrom("QAbstractItemView")
      )
    )

means 'A method with the name dataChanged where that method is on a class 
derived from QAbstractItemView'

You do get interesting compile errors sometimes when writing matcher 
constructs. With enough playing around and reading other matchers I've been 
able to get it to do what I want each time eventually, and the result was 
obvious why it was correct.

Apart from the code I've attached, to see what's possible (built in), you 
can 
reference the following:

# This is the one referenced in the youtube talk above:
http://llvm.org/svn/llvm-project/cfe/branches/tooling/tools/remove-cstr-
calls/RemoveCStrCalls.cpp

# Most of the implementation
http://llvm.org/svn/llvm-
project/cfe/branches/tooling/include/clang/ASTMatchers/ASTMatchers.h

# Unit tests
http://llvm.org/svn/llvm-
project/cfe/branches/tooling/unittests/ASTMatchers/ASTMatchersTest.cpp

[End copy-paste]


I was also wondering if there is an easy way to only modify the code inside 
the repo and not included headers? In my tool I require the user to pass the 
source-dir on the command line, and then manually check paths to see if they 
are below it, but this is error-prone.

Additionally, many of the concepts I encoded are generic, not Qt specific 
(such as renaming methods, enums etc). It seems that these could be re-
usable, so we could either add some version of this to the clang examples, 
and/or see if we can define a higher level collection of ways to refactor 
the code. I presume google also has some collection/repo of such things 
already?

Finally, the suggestion here:

http://thread.gmane.org/gmane.comp.compilers.clang.devel/20433/focus=20759

was not quite what I needed to use to correctly match the code used. The 
matcher I used was this:

  Finder.addMatcher(
    Id("call",
      Call(
        Callee(Function(HasName(QtEscapeFunction))),
        HasArgument(
          0,
          AnyOf(
            BindTemporaryExpression(has(Id("ctor", ConstructorCall()))),
            BindTemporaryExpression(has(Id("operator", 
OverloadedOperatorCall()))),
            Id("operator", OverloadedOperatorCall()),
            Id("ctor", ConstructorCall()),
            Id("expr", Expression())
          )
        )
      )
    ),
    &Callback);

So I had to add some extra BindTemporaryExpression() into the expression 
which I would have preferred to be automatic, like I think some implicit 
cast stuff is.

Thanks for the guidance so far! :),

Steve.





More information about the cfe-dev mailing list