Consider the following example. I use python clang_example.py to parse the header my_source.hpp for function and method declarations.
my_source.hpp
#pragma once
namespace ns {
struct Foo {
struct Bar {};
Bar fun1(void*);
};
using Baz = Foo::Bar;
void fun2(Foo, Baz const&);
}
clang_example.py
I use the following code to parse the function & method declarations using libclang's python bindings:
import clang.cindex
import typing
def filter_node_list_by_predicate(
nodes: typing.Iterable[clang.cindex.Cursor], predicate: typing.Callable
) -> typing.Iterable[clang.cindex.Cursor]:
for i in nodes:
if predicate(i):
yield i
yield from filter_node_list_by_predicate(i.get_children(), predicate)
if __name__ == '__main__':
index = clang.cindex.Index.create()
translation_unit = index.parse('my_source.hpp', args=['-std=c++17'])
for i in filter_node_list_by_predicate(
translation_unit.cursor.get_children(),
lambda n: n.kind in [clang.cindex.CursorKind.FUNCTION_DECL, clang.cindex.CursorKind.CXX_METHOD]
):
print(f"Function name: {i.spelling}")
print(f"\treturn type: \t{i.type.get_result().spelling}")
for arg in i.get_arguments():
print(f"\targ: \t{arg.type.spelling}")
Output
Function name: fun1
return type: Bar
arg: void *
Function name: fun2
return type: void
arg: Foo
arg: const Baz &
Now I would like to extract the fully qualified name of the return type and argument types so I can correctly reference them from the outermost scope:
Function name: ns::Foo::fun1
return type: ns::Foo::Bar
arg: void *
Function name: ns::fun2
return type: void
arg: ns::Foo
arg: const ns::Baz &
Using this SO answer I can get the fully qualified name of the function declaration, but not of the return and argument types.
How do I get the fully qualified name of a type (not a cursor) in clang?
Note:
I tried using Type.get_canonical and it gets me close:
print(f"\treturn type: \t{i.type.get_result().get_canonical().spelling}")
for arg in i.get_arguments():
print(f"\targ: \t{arg.type.get_canonical().spelling}")
But Type.get_canonical also resolves typedefs and aliases, which I do not want. I want the second argument of fun2 to be resolved as const ns::Baz & and not const ns::Foo::Bar &.
EDIT:
After having tested Scott McPeak's answer on my real application case I realized that I need this code to properly resolve template classes and nested types of template classes as well.
Given the above code as well as
namespace ns {
template <typename T>
struct ATemplate {
using value_type = T;
};
typedef ATemplate<Baz> ABaz;
ABaz::value_type fun3();
}
I would want the return type to be resolved to ns::ABaz::value_type and not ns::ATemplate::value_type or ns::ATemplate<ns::Foo::Bar>::value_type. I would be willing to settle for ns::ATemplate<Baz>::value_type.
Also, I can migrate to the C++ API, if the functionality of the Python bindings are too limited for what I want to do.
Unfortunately, there does not appear to be a simple way to print a type using fully-qualified names. Even in the C++ API,
QualType::getAsString(PrintingPolicy&)ignores theSuppressScopeflag due to the intervention of theElaboratedTypePolicyRAIIclass (I don't know why, and the git commit history offers no clues that I could find). Even if the C++ API worked as I would have hoped/expected,PrintingPolicyisn't exposed in the C or Python APIs.Consequently, to do this, we have to resort to taking apart the type structure in the client code, printing fully qualified names whenever we hit a named type, which is typically expressed as
TypeKind.ELABORATED. (I'm not sure if they always are.)The following example program demonstrates the technique, embodied by the
type_strfunction. As a proof of concept, it does not exhaustively handle all of the cases, although it does cover the most common ones. You can look at the source ofTypePrinter.cppto get an idea of what handling all cases entails.On your example input, it prints:
Notably, this fully qualifies
ns::Foo::Barin the return type offun1. It also usesns::Bazin the argument list offun2, rather than using the underlying type,Bar.The revised question asks about a case involving templates and a
typedefthat is used as a scope qualifier, and wants to recover a fully-qualified name that uses thattypedef. This is not possible using the approach outlined above because we construct the qualifiers by walking up the scope stack from the found declaration, ignoring how the type was expressed originally.Using the Python API, it is possible to see the original type syntax and its qualifiers by iterating over children, but the child list is difficult to interpret. For example, if the input is:
and we use this code to print the TU:
then the output is:
Observe that the qualified type
B::Inneris expressed as this pair of adjacent children:There's no simple way to see that the first child is the qualifier portion of the second child. This is a general problem with the Clang C API, and consequently of the Python API: accessors for specific roles are often missing, so one must resort to iterating over children and trying to reverse-engineer which is which. (I spent a couple weeks going down this road for a different project, and eventually had to admit defeat.)
Therefore, with the revised requirement of not merely computing a type syntax string that uses fully-qualified names, but one that adheres to the original syntax as closely as possible, I think it's going to be a difficult task to robustly complete using the Python API since that original syntax is tough to unambiguously retrieve.
I recommend instead using the C++ API. This is still non-trivial, but all the information is there and available through accessors that distinguish the various "child" roles. If you want a tip on getting started, I have a tool on GitHub called
print-clang-astthat prints a lot (but by no means all) of the Clang AST in a moderately readable JSON format. I even just added code to print the details ofNestedNameSpecifier(which is how qualified names are represented) while trying to see if the Python API could be used for what you what. If you try to accomplish this using the C++ API but run into trouble, you could then ask a new question based on where you get stuck.