Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 92 additions & 5 deletions cpp/libclang/integration_test/cases/template_class/expected.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,43 @@
{
"types": {
"ContainerOwner": {
"id": "ContainerOwner",
"name": "ContainerOwner",
"enclosing_namespace_id": null,
"entity_type": "Class",
"enum_literals": [],
"methods": [
{
"name": "values",
"return_type": "Container<int>",
"parameters": [],
"modifiers": [],
"template_parameters": null,
"visibility": "public"
}
],
"relationships": [],
"template_parameters": [
{
"Template": {
"name": "Container",
"parameters": [
{
"Type": {
"name": "T0",
"is_pack": false
}
}
],
"is_pack": false
}
}
],
"type_aliases": [],
"variables": [],
"source_file": "cpp/libclang/integration_test/cases/template_class/transport.cpp",
"source_line": 36
},
"FixedBuffer": {
"id": "FixedBuffer",
"name": "FixedBuffer",
Expand Down Expand Up @@ -32,10 +70,43 @@
{
"name": "convert",
"return_type": "U",
"parameters": [],
"parameters": [
{
"name": "input",
"param_type": "const T &",
"is_variadic": false
}
],
"modifiers": [],
"template_parameters": [
"U"
{
"Type": {
"name": "U",
"is_pack": false
}
}
],
"visibility": "public"
},
{
"name": "emplace",
"return_type": "void",
"parameters": [
{
"name": "args",
"param_type": "Args &&",
"is_variadic": false,
"is_pack_expansion": true
}
],
"modifiers": [],
"template_parameters": [
{
"Type": {
"name": "Args",
"is_pack": true
}
}
],
"visibility": "public"
},
Expand All @@ -50,9 +121,25 @@
],
"relationships": [],
"template_parameters": [
"T",
"int N",
"T2"
{
"Type": {
"name": "T",
"is_pack": false
}
},
{
"NonType": {
"name": "N",
"value_type": "int",
"is_pack": false
}
},
{
"Type": {
"name": "T2",
"is_pack": false
}
}
],
"type_aliases": [],
"variables": [
Expand Down
10 changes: 10 additions & 0 deletions cpp/libclang/integration_test/cases/template_class/transport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,19 @@ class FixedBuffer
template <typename U>
U convert(const T& input) const;

template <typename... Args>
void emplace(Args&&... args);

int capacity() const;

private:
T m_data[N];
int m_size;
};

template <template <typename> class Container>
class ContainerOwner
{
public:
Container<int> values();
};
5 changes: 5 additions & 0 deletions cpp/libclang/src/visitor/src/class_parser_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,10 @@ fn unknown(ty: &Type) -> ResolvedType {
}

fn build_entity_id_from_decl(entity: &Entity) -> String {
if entity.get_kind() == EntityKind::TemplateTemplateParameter {
return entity.get_name().unwrap_or_default();
}

strip_global_scope_prefix(&build_fqn_from_entity(entity))
}

Expand Down Expand Up @@ -640,6 +644,7 @@ fn build_fqn_from_entity(entity: &Entity) -> String {
| EntityKind::UnionDecl
| EntityKind::EnumDecl
| EntityKind::ClassTemplate
| EntityKind::TemplateTemplateParameter
| EntityKind::TypedefDecl
| EntityKind::TypeAliasDecl => {
if let Some(name) = entity.get_name() {
Expand Down
108 changes: 78 additions & 30 deletions cpp/libclang/src/visitor/src/class_visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use std::collections::HashSet;

use class_diagram::{
EntityType, FunctionArgument, MemberVariable, Method, MethodModifier, RelationType,
Relationship, SimpleEntity, TypeAlias, Visibility,
Relationship, SimpleEntity, TemplateParameter, TypeAlias, Visibility,
};

use crate::class_parser_helper::{
Expand Down Expand Up @@ -169,9 +169,7 @@ fn collect_method_type(entity: &Entity, builder: &mut ParsedClassInfo) -> Parsed
.get_result_type()
.map(|t| resolve_type(&t))
.unwrap_or_else(|| ResolvedType::Builtin("void".to_string()));
let parameter_types = entity
.get_arguments()
.unwrap_or_default()
let parameter_types = method_arguments(entity)
.into_iter()
.filter_map(|arg| arg.get_type().map(|t| resolve_type(&t)))
.collect();
Expand All @@ -186,6 +184,22 @@ fn collect_method_type(entity: &Entity, builder: &mut ParsedClassInfo) -> Parsed
parsed_method_type
}

/// Normally libclang provides the parameter list via `Entity::get_arguments()`.
/// However, for some cursor kinds (e.g. `FunctionTemplate`) or certain libclang
/// versions, `get_arguments()` may return `None` even though the AST still
/// contains `ParmDecl` child cursors.
fn method_arguments<'tu>(entity: &Entity<'tu>) -> Vec<Entity<'tu>> {
entity.get_arguments().unwrap_or_else(|| {
// fall back to collecting all direct `ParmDecl` children from
// the cursor to recover the parameter list.
entity
.get_children()
.into_iter()
.filter(|child| child.get_kind() == EntityKind::ParmDecl)
.collect()
})
}

fn parse_type_alias(entity: &Entity) -> Option<TypeAlias> {
let alias = entity.get_name()?;

Expand Down Expand Up @@ -218,20 +232,23 @@ fn parse_method(entity: &Entity, parsed_method_type: &ParsedMethodType) -> Optio
let mut parameters = Vec::new();
let method_is_variadic = entity.get_type().map(|t| t.is_variadic()).unwrap_or(false);

if let Some(args) = entity.get_arguments() {
let arg_count = args.len();
for (idx, arg) in args.into_iter().enumerate() {
let param_type = arg
.get_type()
.map(|ty| ty.get_display_name())
.unwrap_or_default();

parameters.push(FunctionArgument {
name: arg.get_name().unwrap_or_default(),
param_type: Some(param_type),
is_variadic: method_is_variadic && idx + 1 == arg_count,
});
}
let args = method_arguments(entity);

let arg_count = args.len();
for (idx, arg) in args.into_iter().enumerate() {
let raw_param_type = arg
.get_type()
.map(|ty| ty.get_display_name())
.unwrap_or_default();
let is_pack_expansion = raw_param_type.contains("...");
let param_type = normalize_pack_expansion_type(&raw_param_type);

parameters.push(FunctionArgument {
name: arg.get_name().unwrap_or_default(),
param_type: Some(param_type),
is_variadic: method_is_variadic && idx + 1 == arg_count,
is_pack_expansion,
});
}

Some(Method {
Expand Down Expand Up @@ -265,32 +282,47 @@ fn parse_variable(
})
}

fn parse_template_parameters(entity: &Entity) -> Option<Vec<String>> {
let params: Vec<String> = entity
fn parse_template_parameters(entity: &Entity) -> Option<Vec<TemplateParameter>> {
let params: Vec<TemplateParameter> = entity
.get_children()
.into_iter()
.enumerate()
.filter_map(|(idx, child)| match child.get_kind() {
EntityKind::TemplateTypeParameter => {
// template <typename Foo> → "Foo"
// template <typename, typename> -> "T0", "T1"
Some(child.get_name().unwrap_or_else(|| format!("T{idx}")))
// template <typename Foo> → "name: Foo, is_pack: False"
// template <typename, typename> -> "name: T0, is_pack: False", "name: T1, is_pack: False"
// template <typename... Foo> -> "name: Foo, is_pack: True"
let name = child.get_name().unwrap_or_else(|| format!("T{idx}"));

Some(TemplateParameter::Type {
name,
is_pack: is_template_parameter_pack(&child),
})
}
EntityKind::NonTypeTemplateParameter => {
// template <int N> → "int N"
// template <int N> → "name: N, value_type: int"
let type_name = child
.get_type()
.map(|t| t.get_display_name())
.unwrap_or_default();
let name = child.get_name().unwrap_or_default();
Some(format!("{type_name} {name}").trim().to_string())

Some(TemplateParameter::NonType {
name,
value_type: type_name,
is_pack: is_template_parameter_pack(&child),
})
}
EntityKind::TemplateTemplateParameter => {
// template <template<...> class C> → "template<...> C"
Some(format!(
"template<...> {}",
child.get_name().unwrap_or_default()
))
// template <template<...> class C> → "name: C, parameters: [...], is_pack: False"
let parameters = parse_template_parameters(&child).unwrap_or_default();
let name = child.get_name().unwrap_or_else(|| format!("T{idx}"));

Some(TemplateParameter::Template {
name,
parameters,
is_pack: is_template_parameter_pack(&child),
})
}
_ => None,
})
Expand All @@ -303,6 +335,22 @@ fn parse_template_parameters(entity: &Entity) -> Option<Vec<String>> {
}
}

fn normalize_pack_expansion_type(param_type: &str) -> String {
param_type.replace("...", "").trim().to_string()
}

fn is_template_parameter_pack(entity: &Entity) -> bool {
entity.get_range().is_some_and(|range| {
range
.tokenize()
.iter()
.any(|token| token.get_spelling() == "...")
}) || entity
.get_display_name()
.as_deref()
.is_some_and(|display_name| display_name.contains("..."))
}

fn parse_visibility(entity: &Entity) -> Visibility {
match entity.get_accessibility() {
Some(clang::Accessibility::Public) => Visibility::Public,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,18 @@
}
],
"template_parameters": [
"CallableType",
"ArgumentTypes..."
{
"Type": {
"name": "CallableType",
"is_pack": false
}
},
{
"Type": {
"name": "ArgumentTypes",
"is_pack": true
}
}
],
"modifiers": []
},
Expand All @@ -72,7 +82,12 @@
}
],
"template_parameters": [
"T"
{
"Type": {
"name": "T",
"is_pack": false
}
}
],
"modifiers": []
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,12 @@
}
],
"template_parameters": [
"T"
{
"Type": {
"name": "T",
"is_pack": false
}
}
],
"enum_literals": [],
"source_file": "class_diagram_file_level_constructs.puml",
Expand Down
Loading
Loading