use std::{
collections::{HashMap, HashSet},
path::{Path, PathBuf},
sync::Arc,
};
use heck::ToUpperCamelCase;
use serde::Serialize;
use crate::model::{
Comment, DeclareKind, DefineKind, DefineValue, EnumInfo, EnumValueDoc, Expression,
FunctionDeclItem, FunctionDefItem, FunctionDoc, GObjectType, GObjectTypeKind, GType,
InterfaceImplementation, Parameter, PreprocessorDirective, SourceLocation, Statement,
TopLevelItem, TypeDefItem, TypeDoc, TypeInfo, TypedefTarget, UnaryOp, VariableDecl,
};
#[derive(Debug, Clone, Serialize, Default)]
pub struct Project {
#[serde(skip_serializing_if = "HashMap::is_empty")]
pub files: HashMap<PathBuf, FileModel>,
}
impl Project {
pub fn new() -> Self {
Self {
files: HashMap::new(),
}
}
pub fn resolve_all_gobject_types(&mut self) {
let defines: HashMap<String, DefineValue> = self
.files
.values()
.flat_map(FileModel::iter_defines)
.filter_map(|(name, value)| Some((name.to_owned(), value?.clone())))
.collect();
for file in self.files.values_mut() {
file.resolve_gobject_types(&defines);
}
}
pub fn get_file(&self, path: &PathBuf) -> Option<&FileModel> {
self.files.get(path)
}
pub fn is_function_declared_in_header(&self, name: &str) -> bool {
for file in self.files.values() {
if file.path.extension().is_some_and(|ext| ext == "h")
&& file
.iter_function_declarations()
.any(|decl| decl.name == name)
{
return true;
}
}
false
}
pub fn iter_all_files(&self) -> impl Iterator<Item = (&Path, &FileModel)> {
self.files.iter().map(|(path, file)| (path.as_path(), file))
}
pub fn iter_c_files(&self) -> impl Iterator<Item = (&Path, &FileModel)> {
self.files
.iter()
.filter(|(path, _)| path.extension().is_some_and(|ext| ext == "c"))
.map(|(path, file)| (path.as_path(), file))
}
pub fn iter_header_files(&self) -> impl Iterator<Item = (&Path, &FileModel)> {
self.files
.iter()
.filter(|(path, _)| path.extension().is_some_and(|ext| ext == "h"))
.map(|(path, file)| (path.as_path(), file))
}
pub fn find_func_doc(&self, name: &str) -> Option<&FunctionDoc> {
self.iter_c_files()
.flat_map(|(_, f)| f.iter_function_definitions())
.find(|f| f.name == name)
.and_then(|f| f.doc.as_ref())
.or_else(|| {
self.iter_all_files()
.flat_map(|(_, f)| f.iter_function_declarations())
.find(|f| f.name == name)
.and_then(|f| f.doc.as_ref())
})
}
pub fn find_type_doc(&self, type_name: &str) -> Option<&TypeDoc> {
self.iter_c_files()
.flat_map(|(_, f)| f.iter_all_gobject_types())
.find(|gt| gt.type_name == type_name)
.and_then(|gt| gt.doc.as_ref())
.or_else(|| {
self.iter_header_files()
.flat_map(|(_, f)| f.iter_all_gobject_types())
.find(|gt| gt.type_name == type_name)
.and_then(|gt| gt.doc.as_ref())
})
}
pub fn find_gobject_type_by_gtype(&self, gtype: >ype) -> Option<&GObjectType> {
let all_types: Vec<_> = self
.iter_all_files()
.flat_map(|(_, f)| f.iter_all_gobject_types())
.collect();
let declare = all_types
.iter()
.copied()
.find(|gt| gt.type_macro.as_ref() == Some(gtype))?;
all_types
.iter()
.copied()
.find(|gt| gt.type_name == declare.type_name && gt.kind.is_define())
.or(Some(declare))
}
pub fn find_interface_for_property<'a>(
&self,
gobject_type: &'a GObjectType,
property_name: &str,
) -> Option<&'a GType> {
for iface in &gobject_type.interfaces {
let Some(iface_type) = self.find_gobject_type_by_gtype(&iface.interface_type) else {
continue;
};
if iface_type
.properties
.iter()
.any(|p| p.property().name == property_name)
{
return Some(&iface.interface_type);
}
}
None
}
pub fn is_function_exported(&self, name: &str) -> bool {
for file in self.files.values() {
if file
.iter_function_declarations()
.any(|decl| decl.name == name && !decl.export_macros.is_empty())
{
return true;
}
}
false
}
}
pub struct PropertyEnumContext<'a> {
pub gobject_type: &'a GObjectType,
pub class_init: &'a FunctionDefItem,
pub class_type_info: Option<&'a TypeInfo>,
pub get_property_func: Option<&'a str>,
pub set_property_func: Option<&'a str>,
}
#[derive(Debug, Clone, Serialize)]
pub struct FileModel {
pub path: PathBuf,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub top_level_items: Vec<TopLevelItem>,
#[serde(skip)]
pub source: Arc<Vec<u8>>,
}
impl FileModel {
pub fn new(path: PathBuf) -> Self {
Self {
path,
top_level_items: Vec::new(),
source: Arc::new(Vec::new()),
}
}
pub fn iter_all_includes(&self) -> impl Iterator<Item = (&str, bool, SourceLocation)> + '_ {
self.iter_items_recursive(&self.top_level_items)
.filter_map(|item| match item {
TopLevelItem::Preprocessor(PreprocessorDirective::Include {
path,
is_system,
location,
}) => Some((path.as_str(), *is_system, location.clone())),
_ => None,
})
}
pub fn iter_function_definitions(&self) -> impl Iterator<Item = &FunctionDefItem> + '_ {
self.iter_items_recursive(&self.top_level_items)
.filter_map(|item| match item {
TopLevelItem::FunctionDefinition(func) => Some(func),
_ => None,
})
}
pub fn iter_class_init_functions(&self) -> impl Iterator<Item = &FunctionDefItem> + '_ {
self.iter_function_definitions()
.filter(|f| f.name.ends_with("_class_init"))
}
pub fn iter_function_declarations(&self) -> impl Iterator<Item = &FunctionDeclItem> + '_ {
self.iter_items_recursive(&self.top_level_items)
.filter_map(|item| match item {
TopLevelItem::FunctionDeclaration(func) => Some(func),
_ => None,
})
}
pub fn iter_all_function_names(&self) -> impl Iterator<Item = &str> + '_ {
self.iter_items_recursive(&self.top_level_items)
.filter_map(|item| match item {
TopLevelItem::FunctionDefinition(func) => Some(func.name.as_str()),
TopLevelItem::FunctionDeclaration(func) => Some(func.name.as_str()),
_ => None,
})
}
pub fn iter_all_gobject_types(&self) -> impl Iterator<Item = &GObjectType> + '_ {
self.iter_items_recursive(&self.top_level_items)
.filter_map(|item| match item {
TopLevelItem::Preprocessor(PreprocessorDirective::GObjectType(gobject_type)) => {
Some(gobject_type.as_ref())
}
_ => None,
})
}
pub fn find_class_struct_for(&self, gobject_type: &GObjectType) -> Option<&TypeDefItem> {
let name = gobject_type.class_struct_name()?;
self.iter_class_structs().find(|td| {
if let TypeDefItem::Struct { name: n, .. } = td {
n.trim_start_matches('_') == name
} else {
false
}
})
}
pub fn iter_class_structs(&self) -> impl Iterator<Item = &TypeDefItem> + '_ {
self.iter_items_recursive(&self.top_level_items)
.filter_map(|item| match item {
TopLevelItem::TypeDefinition(td @ TypeDefItem::Struct { vfuncs, .. })
if !vfuncs.is_empty() =>
{
Some(td)
}
_ => None,
})
}
pub fn iter_defines(&self) -> impl Iterator<Item = (&str, Option<&DefineValue>)> + '_ {
self.iter_items_recursive(&self.top_level_items)
.filter_map(|item| match item {
TopLevelItem::Preprocessor(PreprocessorDirective::Define {
name, value, ..
}) => Some((name.as_str(), value.as_ref())),
_ => None,
})
}
pub fn iter_comments(&self) -> impl Iterator<Item = &Comment> + '_ {
self.iter_items_recursive(&self.top_level_items)
.filter_map(|item| match item {
TopLevelItem::Comment(c) => Some(c),
_ => None,
})
}
pub fn iter_all_enums(&self) -> impl Iterator<Item = &EnumInfo> + '_ {
self.iter_items_recursive(&self.top_level_items)
.filter_map(|item| match item {
TopLevelItem::TypeDefinition(TypeDefItem::Enum(enum_info)) => {
Some(enum_info.as_ref())
}
_ => None,
})
}
pub fn iter_property_enums(&self) -> impl Iterator<Item = &EnumInfo> + '_ {
self.iter_all_enums().filter(|e| e.is_property_enum())
}
pub fn find_gobject_type_for_signal_enum(&self, enum_info: &EnumInfo) -> Option<&GObjectType> {
let signal_names: Vec<&str> = enum_info
.values
.iter()
.filter(|v| !v.is_signal_last())
.map(|v| v.name.as_str())
.collect();
let n_signals_name = enum_info
.values
.last()
.filter(|v| v.is_signal_last())
.map(|v| v.name.as_str());
let arrays = self.find_typed_arrays("guint", false, n_signals_name);
let array_names: Vec<&str> = arrays.iter().map(|d| d.name.as_str()).collect();
self.iter_all_gobject_types().find(|gt| {
let class_init_name = gt.class_init_function_name();
let Some(func) = self
.iter_function_definitions()
.find(|f| f.name == class_init_name)
else {
return false;
};
if !array_names.is_empty() {
let uses_array = func
.body_statements
.iter()
.flat_map(Statement::iter_assignments)
.any(|a| {
matches!(&*a.lhs, Expression::Subscript(sub)
if matches!(&*sub.array, Expression::Identifier(id)
if array_names.contains(&id.name.as_str())))
});
if uses_array {
return true;
}
}
func.body_statements
.iter()
.flat_map(Statement::iter_assignments)
.any(|a| {
if let Expression::Subscript(sub) = &*a.lhs
&& let Expression::Identifier(index_id) = &*sub.index
&& signal_names.contains(&index_id.name.as_str())
&& let Expression::Call(call) = &*a.rhs
&& call.function_contains("g_signal_new")
{
true
} else {
false
}
})
})
}
pub fn find_typed_arrays(
&self,
base_type: &str,
is_pointer: bool,
sentinel_name: Option<&str>,
) -> Vec<&VariableDecl> {
self.iter_all_items()
.filter_map(|item| {
let TopLevelItem::Declaration(decl) = item else {
return None;
};
if !decl.type_info.is_base_type(base_type)
|| decl.type_info.is_pointer() != is_pointer
{
return None;
}
let matches = match &decl.array_size {
Some(Expression::Identifier(size_id)) => {
sentinel_name.is_none_or(|s| size_id.name == s)
}
Some(Expression::Binary(_)) => sentinel_name.is_none(),
Some(_) => sentinel_name.is_none(),
None => false,
};
matches.then_some(decl.as_ref())
})
.collect()
}
pub fn find_gobject_type_for_property_enum(
&self,
enum_info: &EnumInfo,
) -> Option<&GObjectType> {
let property_names: Vec<&str> = enum_info
.values
.iter()
.filter(|v| !v.is_prop_0() && !v.is_prop_last())
.map(|v| v.name.as_str())
.collect();
let n_props_name = enum_info
.values
.last()
.filter(|v| v.is_prop_last())
.map(|v| v.name.as_str());
self.iter_all_gobject_types().find(|gt| {
let has_matching_property = gt.properties.iter().any(|a| {
a.get_installed_enum_value()
.is_some_and(|ev| property_names.contains(&ev))
});
if has_matching_property {
return true;
}
if let Some(sentinel) = n_props_name {
let class_init_name = gt.class_init_function_name();
if let Some(func) = self
.iter_function_definitions()
.find(|f| f.name == class_init_name)
{
let install_calls = func.find_install_properties_calls();
return install_calls.iter().any(|call| {
call.get_arg(1)
.and_then(|arg| arg.location().as_str())
.is_some_and(|name| name == sentinel)
});
}
}
false
})
}
pub fn resolve_class_init_vfuncs<'a>(
&'a self,
gobject_type: &GObjectType,
) -> HashMap<(String, &'a str), &'a str> {
let class_init_name = gobject_type.class_init_function_name();
let Some(class_init) = self
.iter_function_definitions()
.find(|f| f.name == class_init_name)
else {
return HashMap::new();
};
let mut var_types: HashMap<&str, String> = HashMap::new();
if let Some(Parameter::Regular {
name: Some(name),
type_info,
..
}) = class_init.parameters.first()
{
var_types.insert(name.as_str(), type_info.base_type.clone());
}
for stmt in &class_init.body_statements {
for decl in stmt.iter_declarations() {
if let Some(init) = &decl.initializer
&& let Expression::Call(call) = init
{
let name = call.function_name();
if let Some(prefix) = name
.strip_suffix("_CLASS")
.or_else(|| name.strip_suffix("_GET_CLASS"))
{
var_types.insert(
decl.name.as_str(),
format!("{}Class", prefix.to_upper_camel_case()),
);
continue;
}
}
var_types.insert(decl.name.as_str(), decl.type_info.base_type.clone());
}
}
let func_names: HashSet<&str> = self
.iter_function_definitions()
.map(|f| f.name.as_str())
.collect();
let mut vfuncs = HashMap::new();
for assignment in class_init
.body_statements
.iter()
.flat_map(Statement::iter_assignments)
{
if let Expression::FieldAccess(fa) = &*assignment.lhs
&& let Expression::Identifier(ident) = assignment.rhs.as_ref()
&& func_names.contains(ident.name.as_str())
{
let class_type = match &*fa.base {
Expression::Identifier(id) => var_types.get(id.name.as_str()).cloned(),
Expression::Call(call) => {
let name = call.function_name();
let prefix = name
.strip_suffix("_CLASS")
.or_else(|| name.strip_suffix("_GET_CLASS"));
prefix.map(|p| format!("{}Class", p.to_upper_camel_case()))
}
_ => None,
};
if let Some(ct) = class_type {
vfuncs.insert((ct, fa.field.as_str()), ident.name.as_str());
}
}
}
vfuncs
}
pub fn resolve_property_enum_context(
&self,
enum_info: &EnumInfo,
) -> Option<PropertyEnumContext<'_>> {
let gobject_type = self.find_gobject_type_for_property_enum(enum_info)?;
let class_init_name = gobject_type.class_init_function_name();
let class_init = self
.iter_function_definitions()
.find(|f| f.name == class_init_name)?;
let class_type_info = class_init.parameters.first().and_then(|p| {
if let Parameter::Regular { type_info, .. } = p {
Some(type_info)
} else {
None
}
});
let vfuncs = self.resolve_class_init_vfuncs(gobject_type);
let get_property_func = vfuncs
.iter()
.find(|((_, field), _)| *field == "get_property")
.map(|(_, func)| *func);
let set_property_func = vfuncs
.iter()
.find(|((_, field), _)| *field == "set_property")
.map(|(_, func)| *func);
Some(PropertyEnumContext {
gobject_type,
class_init,
class_type_info,
get_property_func,
set_property_func,
})
}
pub fn iter_all_items(&self) -> impl Iterator<Item = &TopLevelItem> + '_ {
self.iter_items_recursive(&self.top_level_items)
}
pub fn has_declarations(&self) -> bool {
self.iter_all_items().any(|item| {
matches!(
item,
TopLevelItem::TypeDefinition(_)
| TopLevelItem::FunctionDeclaration(_)
| TopLevelItem::FunctionDefinition(_)
| TopLevelItem::Declaration(_)
)
})
}
pub fn iter_typedef_pairs(&self) -> impl Iterator<Item = (&str, &TypeInfo)> + '_ {
self.iter_all_items().filter_map(|item| match item {
TopLevelItem::TypeDefinition(TypeDefItem::Typedef {
name,
target: TypedefTarget::Type(target_type),
struct_fields,
..
}) if struct_fields.is_empty() && !target_type.base_type.is_empty() => {
Some((name.as_str(), target_type))
}
_ => None,
})
}
pub fn resolve_gobject_types(&mut self, defines: &HashMap<String, DefineValue>) {
self.extract_manual_declare_types();
Self::extract_manual_gobject_types(&mut self.top_level_items);
Self::resolve_items(&mut self.top_level_items, defines);
}
fn extract_manual_declare_types(&mut self) {
let existing_macros: Vec<GType> = self
.iter_all_items()
.filter_map(|item| match item {
TopLevelItem::Preprocessor(PreprocessorDirective::GObjectType(gt)) => {
gt.type_macro.clone()
}
_ => None,
})
.collect();
struct DefineInfo {
name: String,
value: DefineValue,
location: SourceLocation,
}
let defines: Vec<DefineInfo> = self
.iter_all_items()
.filter_map(|item| match item {
TopLevelItem::Preprocessor(PreprocessorDirective::Define {
name,
value: Some(value),
location,
}) => Some(DefineInfo {
name: name.clone(),
value: value.clone(),
location: location.clone(),
}),
_ => None,
})
.collect();
let mut new_types = Vec::new();
for define in &defines {
if !define.name.contains("_TYPE_") {
continue;
}
let gtype = GType::Identifier(define.name.clone());
if existing_macros.contains(>ype) {
continue;
}
let raw = define.value.as_raw_str();
let trimmed = raw.trim().trim_matches(|c| c == '(' || c == ')');
let Some(func_name) = trimmed.split_whitespace().next() else {
continue;
};
let Some(function_prefix) = func_name.strip_suffix("_get_type") else {
continue;
};
let Some((module_prefix, type_suffix)) = define.name.split_once("_TYPE_") else {
continue;
};
let cast_macro_name = format!("{}_{}", module_prefix, type_suffix);
let type_name = defines
.iter()
.find(|d| d.name == cast_macro_name)
.and_then(|d| {
d.value
.as_raw_str()
.rsplit(',')
.next()
.map(|s| s.trim().trim_end_matches(')').trim().to_owned())
});
let Some(type_name) = type_name else {
continue;
};
if type_name.is_empty() || !type_name.chars().next().unwrap().is_uppercase() {
continue;
}
let already_exists = self.iter_all_items().any(|item| {
matches!(
item,
TopLevelItem::Preprocessor(PreprocessorDirective::GObjectType(gt))
if gt.type_name == type_name
)
});
if already_exists {
continue;
}
new_types.push(TopLevelItem::Preprocessor(
PreprocessorDirective::GObjectType(Box::new(GObjectType {
type_name,
type_macro: Some(gtype),
function_prefix: function_prefix.to_owned(),
parent_type: None,
flags: None,
kind: GObjectTypeKind::Declare {
kind: DeclareKind::Derivable,
module_prefix: module_prefix.to_owned(),
type_prefix: type_suffix.to_owned(),
},
interfaces: Vec::new(),
has_private: false,
manually_registered: true,
code_block_statements: Vec::new(),
export_macros: Vec::new(),
doc: None,
properties: Vec::new(),
signals: Vec::new(),
location: define.location.clone(),
})),
));
}
self.top_level_items.extend(new_types);
}
fn extract_manual_gobject_types(items: &mut Vec<TopLevelItem>) {
let existing_type_names: Vec<String> = items
.iter()
.filter_map(|item| match item {
TopLevelItem::Preprocessor(PreprocessorDirective::GObjectType(gt)) => {
Some(gt.type_name.clone())
}
_ => None,
})
.collect();
let mut new_types = Vec::new();
for item in items.iter() {
let TopLevelItem::FunctionDefinition(func) = item else {
continue;
};
if !func.name.ends_with("_get_type") || func.return_type.base_type != "GType" {
continue;
}
let function_prefix = func.name.strip_suffix("_get_type").unwrap();
let register_call = func.find_calls_matching(|name| {
name == "g_type_register_static" || name == "g_type_register_static_simple"
});
let Some(call) = register_call.first() else {
continue;
};
let Some(parent_expr) = call.get_arg(0) else {
continue;
};
let parent_type = match parent_expr {
Expression::Identifier(id) => id.name.clone(),
_ => continue,
};
let Some(type_name) = call.extract_string_from_arg(1) else {
continue;
};
let type_name = type_name.trim_matches('"').to_owned();
if existing_type_names.contains(&type_name) {
continue;
}
let flags = call
.get_arg(if call.arguments.len() > 4 { 6 } else { 3 })
.and_then(|e| match e {
Expression::Identifier(id) => Some(id.name.as_str()),
_ => None,
});
let is_interface = parent_type == "G_TYPE_INTERFACE";
let is_abstract = flags.is_some_and(|f| f.contains("ABSTRACT"));
let has_private = !func.find_calls(&["g_type_add_instance_private"]).is_empty();
let kind = if is_interface {
GObjectTypeKind::Define(DefineKind::Interface)
} else if is_abstract {
GObjectTypeKind::Define(DefineKind::AbstractType)
} else {
GObjectTypeKind::Define(DefineKind::Type)
};
let iface_calls = func.find_calls(&["g_type_add_interface_static"]);
let mut interfaces = Vec::new();
for iface_call in &iface_calls {
let Some(iface_type_expr) = iface_call.get_arg(1) else {
continue;
};
let Expression::Identifier(iface_id) = iface_type_expr else {
continue;
};
let interface_type = GType::Identifier(iface_id.name.clone());
let init_function = iface_call
.get_arg(2)
.and_then(|e| match e {
Expression::Unary(u) if u.operator == UnaryOp::AddressOf => {
match u.operand.as_ref() {
Expression::Identifier(id) => Some(id.name.as_str()),
_ => None,
}
}
_ => None,
})
.and_then(|var_name| {
Self::find_init_func_from_iface_info(&func.body_statements, var_name)
});
interfaces.push(InterfaceImplementation {
interface_type,
init_function,
});
}
new_types.push(TopLevelItem::Preprocessor(
PreprocessorDirective::GObjectType(Box::new(GObjectType {
type_name,
type_macro: None,
function_prefix: function_prefix.to_owned(),
parent_type: Some(parent_type),
flags: flags.map(std::borrow::ToOwned::to_owned),
kind,
interfaces,
has_private,
manually_registered: true,
code_block_statements: Vec::new(),
export_macros: Vec::new(),
doc: None,
properties: Vec::new(),
signals: Vec::new(),
location: func.location.clone(),
})),
));
}
items.extend(new_types);
}
fn find_init_func_from_iface_info(stmts: &[Statement], var_name: &str) -> Option<String> {
for stmt in stmts {
match stmt {
Statement::Declaration(decl) if decl.name == var_name => {
let Expression::InitializerList(init) = decl.initializer.as_ref()? else {
return None;
};
let first = init
.items
.iter()
.find(|item| !matches!(&*item.value, Expression::Comment(_)))?;
return Some(Self::unwrap_cast_to_identifier(&first.value)?.to_owned());
}
Statement::If(if_stmt) => {
if let Some(v) =
Self::find_init_func_from_iface_info(&if_stmt.then_body, var_name)
{
return Some(v);
}
if let Some(else_body) = &if_stmt.else_body
&& let Some(v) = Self::find_init_func_from_iface_info(else_body, var_name)
{
return Some(v);
}
}
Statement::Compound(c) => {
if let Some(v) = Self::find_init_func_from_iface_info(&c.statements, var_name) {
return Some(v);
}
}
_ => {}
}
}
None
}
fn unwrap_cast_to_identifier(expr: &Expression) -> Option<&str> {
match expr {
Expression::Identifier(id) => Some(&id.name),
Expression::Cast(c) => Self::unwrap_cast_to_identifier(&c.operand),
_ => None,
}
}
fn resolve_items(items: &mut [TopLevelItem], defines: &HashMap<String, DefineValue>) {
for i in 0..items.len() {
match &items[i] {
TopLevelItem::Preprocessor(PreprocessorDirective::GObjectType(gt)) => {
let type_name = gt.type_name.clone();
let init_names = if gt.is_interface() {
vec![
gt.default_init_function_name(),
gt.class_init_function_name(),
]
} else {
vec![gt.class_init_function_name()]
};
let func_idx = items.iter().position(|item| {
matches!(item, TopLevelItem::FunctionDefinition(f) if init_names.contains(&f.name))
});
if let Some(j) = func_idx {
let func = match &items[j] {
TopLevelItem::FunctionDefinition(f) => f,
_ => unreachable!(),
};
let properties = func.find_param_spec_assignments(&type_name, defines);
let signals = func.find_signal_registrations(&type_name);
if let TopLevelItem::Preprocessor(PreprocessorDirective::GObjectType(gt)) =
&mut items[i]
{
gt.properties = properties;
gt.signals = signals;
}
}
if let Some(doc) = Self::find_type_doc_in_comments(items, &type_name)
&& let TopLevelItem::Preprocessor(PreprocessorDirective::GObjectType(gt)) =
&mut items[i]
{
gt.doc = Some(doc);
}
}
TopLevelItem::Preprocessor(PreprocessorDirective::Conditional { .. })
| TopLevelItem::Preprocessor(PreprocessorDirective::GObjectDeclsBlock { .. }) => {}
_ => {}
}
}
Self::resolve_type_docs(items);
for item in items.iter_mut() {
match item {
TopLevelItem::Preprocessor(PreprocessorDirective::Conditional { body, .. })
| TopLevelItem::Preprocessor(PreprocessorDirective::GObjectDeclsBlock {
body,
..
}) => {
Self::resolve_items(body, defines);
}
_ => {}
}
}
}
fn find_type_doc_in_comments(items: &[TopLevelItem], type_name: &str) -> Option<TypeDoc> {
items.iter().find_map(|item| {
if let TopLevelItem::Comment(c) = item {
let doc = TypeDoc::from_comment(c)?;
let sym = doc.symbol.as_deref()?;
if sym == type_name {
return Some(doc);
}
}
None
})
}
fn resolve_type_docs(items: &mut [TopLevelItem]) {
for i in 0..items.len() {
let name = match &items[i] {
TopLevelItem::TypeDefinition(
TypeDefItem::Struct {
name, doc: None, ..
}
| TypeDefItem::Typedef {
name, doc: None, ..
},
) => Some(name.trim_start_matches('_').to_owned()),
TopLevelItem::TypeDefinition(TypeDefItem::Enum(e)) if e.doc.is_none() => {
e.name.clone()
}
_ => None,
};
if let Some(name) = name
&& let Some(doc) = Self::find_type_doc_in_comments(items, &name)
{
match &mut items[i] {
TopLevelItem::TypeDefinition(
TypeDefItem::Struct { doc: d, .. } | TypeDefItem::Typedef { doc: d, .. },
) => {
*d = Some(doc);
}
TopLevelItem::TypeDefinition(TypeDefItem::Enum(e)) => {
e.doc = Some(doc);
}
_ => {}
}
}
}
Self::resolve_enum_value_docs(items);
}
fn resolve_enum_value_docs(items: &mut [TopLevelItem]) {
for i in 0..items.len() {
let TopLevelItem::TypeDefinition(TypeDefItem::Enum(e)) = &mut items[i] else {
continue;
};
let enum_name = match &e.name {
Some(n) => n.clone(),
None => continue,
};
let inline_docs: Vec<(String, EnumValueDoc)> = items
.iter()
.find_map(|item| {
if let TopLevelItem::Comment(c) = item {
let doc = TypeDoc::from_comment(c)?;
if doc.symbol.as_deref() == Some(&enum_name) {
return Some(EnumValueDoc::extract_inline_from_comment(c));
}
}
None
})
.unwrap_or_default();
let TopLevelItem::TypeDefinition(TypeDefItem::Enum(e)) = &mut items[i] else {
continue;
};
for value in &mut e.values {
if let Some((_, doc)) = inline_docs.iter().find(|(n, _)| *n == value.name) {
value.doc = Some(doc.clone());
}
}
let missing: Vec<String> = e
.values
.iter()
.filter(|v| v.doc.is_none())
.map(|v| v.name.clone())
.collect();
let standalone_docs: Vec<(String, EnumValueDoc)> = missing
.iter()
.filter_map(|name| {
let doc = items.iter().find_map(|item| {
if let TopLevelItem::Comment(c) = item {
let doc = EnumValueDoc::from_comment(c)?;
if doc.symbol.as_deref() == Some(name.as_str()) {
return Some(doc);
}
}
None
})?;
Some((name.clone(), doc))
})
.collect();
let TopLevelItem::TypeDefinition(TypeDefItem::Enum(e)) = &mut items[i] else {
continue;
};
for (name, doc) in standalone_docs {
if let Some(value) = e.values.iter_mut().find(|v| v.name == name) {
value.doc = Some(doc);
}
}
}
}
fn iter_items_recursive<'a>(
&'a self,
items: &'a [TopLevelItem],
) -> Box<dyn Iterator<Item = &'a TopLevelItem> + 'a> {
Box::new(items.iter().flat_map(move |item| match item {
TopLevelItem::Preprocessor(PreprocessorDirective::Conditional { body, .. })
| TopLevelItem::Preprocessor(PreprocessorDirective::GObjectDeclsBlock {
body, ..
}) => Box::new(std::iter::once(item).chain(self.iter_items_recursive(body)))
as Box<dyn Iterator<Item = &'a TopLevelItem>>,
_ => Box::new(std::iter::once(item)) as Box<dyn Iterator<Item = &'a TopLevelItem>>,
}))
}
fn iter_items_with_parent<'a>(
&'a self,
items: &'a [TopLevelItem],
parent: Option<&'a TopLevelItem>,
) -> Box<dyn Iterator<Item = (&'a TopLevelItem, Option<&'a TopLevelItem>)> + 'a> {
Box::new(items.iter().flat_map(move |item| {
match item {
TopLevelItem::Preprocessor(PreprocessorDirective::Conditional { body, .. })
| TopLevelItem::Preprocessor(PreprocessorDirective::GObjectDeclsBlock {
body,
..
}) => Box::new(
std::iter::once((item, parent))
.chain(self.iter_items_with_parent(body, Some(item))),
)
as Box<dyn Iterator<Item = (&'a TopLevelItem, Option<&'a TopLevelItem>)>>,
_ => Box::new(std::iter::once((item, parent)))
as Box<dyn Iterator<Item = (&'a TopLevelItem, Option<&'a TopLevelItem>)>>,
}
}))
}
pub fn iter_all_items_with_parent(
&self,
) -> impl Iterator<Item = (&TopLevelItem, Option<&TopLevelItem>)> + '_ {
self.iter_items_with_parent(&self.top_level_items, None)
}
}