如何用libclang Python绑定获取类型的完全限定名(含模板类)
问题背景
我通过执行python clang_example.py解析头文件my_source.hpp中的函数和方法声明,现有代码只能获取非限定的类型名,无法得到从最外层作用域可直接引用的完全限定名,同时需要保留typedef和别名,不能解析到它们的底层类型。
原头文件 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
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}")
现有输出
Function name: fun1 return type: Bar arg: void * Function name: fun2 return type: void arg: Foo arg: const Baz &
期望输出
需要获取函数、返回类型和参数类型的完全限定名,同时保留typedef/别名,输出如下:
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 &
尝试过的方法
我试过使用Type.get_canonical(),代码如下:
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}")
但该方法会解析typedef和别名,比如fun2的第二个参数会被解析为const ns::Foo::Bar &,不符合保留const ns::Baz &的需求。
补充需求(模板场景)
实际应用中还需要正确解析模板类及嵌套类型,例如以下代码:
namespace ns { template <typename T> struct ATemplate { using value_type = T; }; typedef ATemplate<Baz> ABaz; ABaz::value_type fun3(); }
期望返回类型解析为ns::ABaz::value_type,退而求其次可接受ns::ATemplate<Baz>::value_type,但不能是ns::ATemplate::value_type或ns::ATemplate<ns::Foo::Bar>::value_type。
若Python绑定功能不足,可迁移到C++ API。
解决方案
1. Python绑定实现思路
要获取保留typedef/别名的完全限定类型名,核心是通过类型对应的**游标(Cursor)**来获取其限定名,而非直接使用Type.spelling或get_canonical()。步骤如下:
- 对于任意
Type对象,先通过get_declaration()获取其对应的声明游标; - 若游标有效,获取游标的完全限定名(通过
get_qualified_name()方法); - 处理类型的修饰符(如
const、&、*等),将限定名与修饰符组合; - 特殊处理内置类型(如
void),直接返回其拼写。
修改后的Python代码示例:
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) def get_qualified_type_name(typ: clang.cindex.Type) -> str: # 处理类型修饰符的辅助函数 def get_type_modifiers(t: clang.cindex.Type) -> str: modifiers = [] # 处理引用 while t.kind == clang.cindex.TypeKind.LVALUEREFERENCE or t.kind == clang.cindex.TypeKind.RVALUEREFERENCE: modifiers.append("&" if t.kind == clang.cindex.TypeKind.LVALUEREFERENCE else "&&") t = t.get_pointee() # 处理指针 while t.kind == clang.cindex.TypeKind.POINTER: modifiers.append("*") t = t.get_pointee() # 处理const/volatile if t.is_const_qualified(): modifiers.insert(0, "const") if t.is_volatile_qualified(): modifiers.insert(0, "volatile") return " ".join(modifiers) base_type = typ # 先剥离所有修饰符,获取基础类型 while (base_type.kind in [clang.cindex.TypeKind.LVALUEREFERENCE, clang.cindex.TypeKind.RVALUEREFERENCE, clang.cindex.TypeKind.POINTER]): base_type = base_type.get_pointee() # 获取基础类型的声明游标 decl_cursor = base_type.get_declaration() modifiers = get_type_modifiers(typ) # 处理内置类型(无有效声明游标) if decl_cursor.kind == clang.cindex.CursorKind.NO_DECL_FOUND: type_name = base_type.spelling else: # 获取完全限定名 type_name = decl_cursor.get_qualified_name() # 组合修饰符和类型名 if modifiers: # 处理const &这种顺序 if "const" in modifiers and ("&" in modifiers or "*" in modifiers): return f"{modifiers} {type_name}" return f"{type_name} {modifiers}" return type_name def get_qualified_function_name(cursor: clang.cindex.Cursor) -> str: return cursor.get_qualified_name() if __name__ == '__main__': index = clang.cindex.Index.create() translation_unit = index.parse('my_source.hpp', args=['-std=c++17']) for func_cursor 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: {get_qualified_function_name(func_cursor)}") ret_type = func_cursor.type.get_result() print(f"\treturn type: \t{get_qualified_type_name(ret_type)}") for arg in func_cursor.get_arguments(): arg_type = arg.type print(f"\targ: \t{get_qualified_type_name(arg_type)}")
2. C++ API实现思路
若Python绑定的功能不足以处理复杂模板场景,可使用Clang C++ API,核心逻辑一致:
- 通过
Type::getAs<Decl>()获取类型对应的声明; - 使用
PrintingPolicy控制输出格式,禁用SuppressScope和SuppressUnwrittenScope,同时设置相关选项以保留typedef/别名; - 使用
Type::getAsString(const PrintingPolicy &)生成符合要求的类型名。
示例代码片段:
#include <clang/AST/ASTContext.h> #include <clang/AST/Type.h> #include <clang/AST/Decl.h> #include <clang/AST/PrettyPrinter.h> std::string getQualifiedTypeName(const clang::Type *type, clang::ASTContext &ctx) { clang::PrintingPolicy policy = ctx.getPrintingPolicy(); // 配置打印策略:保留限定名,不解析typedef policy.SuppressScope = false; policy.SuppressUnwrittenScope = false; policy.TerseOutput = false; policy.IncludeTagDefinitions = false; // 对于模板,保留原typedef名称而非展开 policy.SuppressTemplateArgsInCXXConstructors = false; return type->getAsString(policy); } // 获取函数的完全限定名 std::string getQualifiedFunctionName(clang::FunctionDecl *funcDecl) { return funcDecl->getQualifiedNameAsString(); }
3. 模板场景处理说明
对于补充的模板代码场景,上述方案可以保留ABaz的typedef名称:
- Python代码中,
ABaz::value_type的声明游标会指向ABaz内部的value_type别名,其完全限定名为ns::ABaz::value_type; - C++ API通过
PrintingPolicy的配置,会直接输出ns::ABaz::value_type,符合需求。
内容的提问来源于stack exchange,提问作者joergbrech

