Skip to main content

gobject_ast/model/
project.rs

1use std::{
2    collections::{HashMap, HashSet},
3    path::{Path, PathBuf},
4    sync::Arc,
5};
6
7use heck::ToUpperCamelCase;
8use serde::Serialize;
9
10use crate::model::{
11    Comment, DeclareKind, DefineKind, DefineValue, EnumInfo, EnumValueDoc, Expression,
12    FunctionDeclItem, FunctionDefItem, FunctionDoc, GObjectType, GObjectTypeKind, GType,
13    InterfaceImplementation, Parameter, PreprocessorDirective, SourceLocation, Statement,
14    TopLevelItem, TypeDefItem, TypeDoc, TypeInfo, TypedefTarget, UnaryOp, VariableDecl,
15};
16
17/// The complete project model - a map of files to their content
18#[derive(Debug, Clone, Serialize, Default)]
19pub struct Project {
20    #[serde(skip_serializing_if = "HashMap::is_empty")]
21    pub files: HashMap<PathBuf, FileModel>,
22}
23
24impl Project {
25    pub fn new() -> Self {
26        Self {
27            files: HashMap::new(),
28        }
29    }
30
31    /// Build a project-wide define map and resolve GObject types across all
32    /// files. Must be called after all files have been parsed.
33    pub fn resolve_all_gobject_types(&mut self) {
34        let defines: HashMap<String, DefineValue> = self
35            .files
36            .values()
37            .flat_map(FileModel::iter_defines)
38            .filter_map(|(name, value)| Some((name.to_owned(), value?.clone())))
39            .collect();
40        for file in self.files.values_mut() {
41            file.resolve_gobject_types(&defines);
42        }
43    }
44
45    /// Get a file's model
46    pub fn get_file(&self, path: &PathBuf) -> Option<&FileModel> {
47        self.files.get(path)
48    }
49
50    /// Check if a function is declared in any header
51    pub fn is_function_declared_in_header(&self, name: &str) -> bool {
52        for file in self.files.values() {
53            if file.path.extension().is_some_and(|ext| ext == "h")
54                && file
55                    .iter_function_declarations()
56                    .any(|decl| decl.name == name)
57            {
58                return true;
59            }
60        }
61        false
62    }
63
64    pub fn iter_all_files(&self) -> impl Iterator<Item = (&Path, &FileModel)> {
65        self.files.iter().map(|(path, file)| (path.as_path(), file))
66    }
67
68    pub fn iter_c_files(&self) -> impl Iterator<Item = (&Path, &FileModel)> {
69        self.files
70            .iter()
71            .filter(|(path, _)| path.extension().is_some_and(|ext| ext == "c"))
72            .map(|(path, file)| (path.as_path(), file))
73    }
74
75    pub fn iter_header_files(&self) -> impl Iterator<Item = (&Path, &FileModel)> {
76        self.files
77            .iter()
78            .filter(|(path, _)| path.extension().is_some_and(|ext| ext == "h"))
79            .map(|(path, file)| (path.as_path(), file))
80    }
81
82    pub fn find_func_doc(&self, name: &str) -> Option<&FunctionDoc> {
83        self.iter_c_files()
84            .flat_map(|(_, f)| f.iter_function_definitions())
85            .find(|f| f.name == name)
86            .and_then(|f| f.doc.as_ref())
87            .or_else(|| {
88                self.iter_all_files()
89                    .flat_map(|(_, f)| f.iter_function_declarations())
90                    .find(|f| f.name == name)
91                    .and_then(|f| f.doc.as_ref())
92            })
93    }
94
95    pub fn find_type_doc(&self, type_name: &str) -> Option<&TypeDoc> {
96        self.iter_c_files()
97            .flat_map(|(_, f)| f.iter_all_gobject_types())
98            .find(|gt| gt.type_name == type_name)
99            .and_then(|gt| gt.doc.as_ref())
100            .or_else(|| {
101                self.iter_header_files()
102                    .flat_map(|(_, f)| f.iter_all_gobject_types())
103                    .find(|gt| gt.type_name == type_name)
104                    .and_then(|gt| gt.doc.as_ref())
105            })
106    }
107
108    pub fn find_gobject_type_by_gtype(&self, gtype: &GType) -> Option<&GObjectType> {
109        let all_types: Vec<_> = self
110            .iter_all_files()
111            .flat_map(|(_, f)| f.iter_all_gobject_types())
112            .collect();
113
114        let declare = all_types
115            .iter()
116            .copied()
117            .find(|gt| gt.type_macro.as_ref() == Some(gtype))?;
118
119        all_types
120            .iter()
121            .copied()
122            .find(|gt| gt.type_name == declare.type_name && gt.kind.is_define())
123            .or(Some(declare))
124    }
125
126    /// Given a GObjectType and its overridden property names, find the
127    /// implemented interface that defines the most of them. Returns `None`
128    /// if no interface matches any property, or if there's a tie.
129    pub fn find_interface_for_property<'a>(
130        &self,
131        gobject_type: &'a GObjectType,
132        property_name: &str,
133    ) -> Option<&'a GType> {
134        for iface in &gobject_type.interfaces {
135            let Some(iface_type) = self.find_gobject_type_by_gtype(&iface.interface_type) else {
136                continue;
137            };
138            if iface_type
139                .properties
140                .iter()
141                .any(|p| p.property().name == property_name)
142            {
143                return Some(&iface.interface_type);
144            }
145        }
146        None
147    }
148
149    /// Check if a function has export macros (truly public API)
150    pub fn is_function_exported(&self, name: &str) -> bool {
151        for file in self.files.values() {
152            if file
153                .iter_function_declarations()
154                .any(|decl| decl.name == name && !decl.export_macros.is_empty())
155            {
156                return true;
157            }
158        }
159        false
160    }
161}
162
163/// Resolved context for a property enum: the owning GObjectType, its class_init
164/// function, and the get/set property function names.
165pub struct PropertyEnumContext<'a> {
166    pub gobject_type: &'a GObjectType,
167    pub class_init: &'a FunctionDefItem,
168    pub class_type_info: Option<&'a TypeInfo>,
169    pub get_property_func: Option<&'a str>,
170    pub set_property_func: Option<&'a str>,
171}
172
173/// Model of a single file (header or C file)
174#[derive(Debug, Clone, Serialize)]
175pub struct FileModel {
176    pub path: PathBuf,
177    /// Top-level items in source order (preserves structure like #ifdef blocks)
178    #[serde(skip_serializing_if = "Vec::is_empty")]
179    pub top_level_items: Vec<TopLevelItem>,
180    /// The raw source code of this file - available for detailed pattern
181    /// matching
182    #[serde(skip)]
183    pub source: Arc<Vec<u8>>,
184}
185
186impl FileModel {
187    pub fn new(path: PathBuf) -> Self {
188        Self {
189            path,
190            top_level_items: Vec::new(),
191            source: Arc::new(Vec::new()),
192        }
193    }
194
195    /// Iterate through all includes in the file (including those in #ifdef
196    /// blocks)
197    pub fn iter_all_includes(&self) -> impl Iterator<Item = (&str, bool, SourceLocation)> + '_ {
198        self.iter_items_recursive(&self.top_level_items)
199            .filter_map(|item| match item {
200                TopLevelItem::Preprocessor(PreprocessorDirective::Include {
201                    path,
202                    is_system,
203                    location,
204                }) => Some((path.as_str(), *is_system, location.clone())),
205                _ => None,
206            })
207    }
208
209    /// Iterate through all function definitions in the file (including those in
210    /// #ifdef blocks)
211    pub fn iter_function_definitions(&self) -> impl Iterator<Item = &FunctionDefItem> + '_ {
212        self.iter_items_recursive(&self.top_level_items)
213            .filter_map(|item| match item {
214                TopLevelItem::FunctionDefinition(func) => Some(func),
215                _ => None,
216            })
217    }
218
219    /// Iterate through class_init functions
220    pub fn iter_class_init_functions(&self) -> impl Iterator<Item = &FunctionDefItem> + '_ {
221        self.iter_function_definitions()
222            .filter(|f| f.name.ends_with("_class_init"))
223    }
224
225    /// Iterate through all function declarations in the file (including those
226    /// in #ifdef blocks)
227    pub fn iter_function_declarations(&self) -> impl Iterator<Item = &FunctionDeclItem> + '_ {
228        self.iter_items_recursive(&self.top_level_items)
229            .filter_map(|item| match item {
230                TopLevelItem::FunctionDeclaration(func) => Some(func),
231                _ => None,
232            })
233    }
234
235    /// Iterate through all functions (both declarations and definitions),
236    /// returning function names
237    pub fn iter_all_function_names(&self) -> impl Iterator<Item = &str> + '_ {
238        self.iter_items_recursive(&self.top_level_items)
239            .filter_map(|item| match item {
240                TopLevelItem::FunctionDefinition(func) => Some(func.name.as_str()),
241                TopLevelItem::FunctionDeclaration(func) => Some(func.name.as_str()),
242                _ => None,
243            })
244    }
245
246    /// Iterate through all GObject type declarations (including those in #ifdef
247    /// blocks)
248    pub fn iter_all_gobject_types(&self) -> impl Iterator<Item = &GObjectType> + '_ {
249        self.iter_items_recursive(&self.top_level_items)
250            .filter_map(|item| match item {
251                TopLevelItem::Preprocessor(PreprocessorDirective::GObjectType(gobject_type)) => {
252                    Some(gobject_type.as_ref())
253                }
254                _ => None,
255            })
256    }
257
258    /// Find the class or interface struct for a given `GObjectType`.
259    /// Returns `None` for final types (no class struct) and when the struct
260    /// was not found in this file.
261    pub fn find_class_struct_for(&self, gobject_type: &GObjectType) -> Option<&TypeDefItem> {
262        let name = gobject_type.class_struct_name()?;
263        self.iter_class_structs().find(|td| {
264            if let TypeDefItem::Struct { name: n, .. } = td {
265                n.trim_start_matches('_') == name
266            } else {
267                false
268            }
269        })
270    }
271
272    /// Iterate through all class structs (structs ending with `Class` or
273    /// `Interface` that have at least one vfunc).
274    pub fn iter_class_structs(&self) -> impl Iterator<Item = &TypeDefItem> + '_ {
275        self.iter_items_recursive(&self.top_level_items)
276            .filter_map(|item| match item {
277                TopLevelItem::TypeDefinition(td @ TypeDefItem::Struct { vfuncs, .. })
278                    if !vfuncs.is_empty() =>
279                {
280                    Some(td)
281                }
282                _ => None,
283            })
284    }
285
286    pub fn iter_defines(&self) -> impl Iterator<Item = (&str, Option<&DefineValue>)> + '_ {
287        self.iter_items_recursive(&self.top_level_items)
288            .filter_map(|item| match item {
289                TopLevelItem::Preprocessor(PreprocessorDirective::Define {
290                    name, value, ..
291                }) => Some((name.as_str(), value.as_ref())),
292                _ => None,
293            })
294    }
295
296    /// Iterate through all standalone comments (including those in #ifdef
297    /// blocks)
298    pub fn iter_comments(&self) -> impl Iterator<Item = &Comment> + '_ {
299        self.iter_items_recursive(&self.top_level_items)
300            .filter_map(|item| match item {
301                TopLevelItem::Comment(c) => Some(c),
302                _ => None,
303            })
304    }
305
306    /// Iterate through all enum definitions (including those in #ifdef blocks)
307    pub fn iter_all_enums(&self) -> impl Iterator<Item = &EnumInfo> + '_ {
308        self.iter_items_recursive(&self.top_level_items)
309            .filter_map(|item| match item {
310                TopLevelItem::TypeDefinition(TypeDefItem::Enum(enum_info)) => {
311                    Some(enum_info.as_ref())
312                }
313                _ => None,
314            })
315    }
316
317    /// Iterate through property enums (enums that appear to define GObject
318    /// properties) Filters for enums where first member starts with PROP_
319    /// or ends with _PROP_0
320    pub fn iter_property_enums(&self) -> impl Iterator<Item = &EnumInfo> + '_ {
321        self.iter_all_enums().filter(|e| e.is_property_enum())
322    }
323
324    /// Find the GObjectType whose class_init installs signals from the given
325    /// signal enum.
326    pub fn find_gobject_type_for_signal_enum(&self, enum_info: &EnumInfo) -> Option<&GObjectType> {
327        let signal_names: Vec<&str> = enum_info
328            .values
329            .iter()
330            .filter(|v| !v.is_signal_last())
331            .map(|v| v.name.as_str())
332            .collect();
333
334        let n_signals_name = enum_info
335            .values
336            .last()
337            .filter(|v| v.is_signal_last())
338            .map(|v| v.name.as_str());
339
340        let arrays = self.find_typed_arrays("guint", false, n_signals_name);
341        let array_names: Vec<&str> = arrays.iter().map(|d| d.name.as_str()).collect();
342
343        self.iter_all_gobject_types().find(|gt| {
344            let class_init_name = gt.class_init_function_name();
345            let Some(func) = self
346                .iter_function_definitions()
347                .find(|f| f.name == class_init_name)
348            else {
349                return false;
350            };
351
352            // Check if class_init assigns to the signal array
353            if !array_names.is_empty() {
354                let uses_array = func
355                    .body_statements
356                    .iter()
357                    .flat_map(Statement::iter_assignments)
358                    .any(|a| {
359                        matches!(&*a.lhs, Expression::Subscript(sub)
360                            if matches!(&*sub.array, Expression::Identifier(id)
361                                if array_names.contains(&id.name.as_str())))
362                    });
363                if uses_array {
364                    return true;
365                }
366            }
367
368            // Check if class_init uses signal enum values in subscript assignments
369            // with g_signal_new
370            func.body_statements
371                .iter()
372                .flat_map(Statement::iter_assignments)
373                .any(|a| {
374                    if let Expression::Subscript(sub) = &*a.lhs
375                        && let Expression::Identifier(index_id) = &*sub.index
376                        && signal_names.contains(&index_id.name.as_str())
377                        && let Expression::Call(call) = &*a.rhs
378                        && call.function_contains("g_signal_new")
379                    {
380                        true
381                    } else {
382                        false
383                    }
384                })
385        })
386    }
387
388    /// Find array declarations of a specific type, optionally filtered by
389    /// sentinel
390    ///
391    /// If sentinel_name is None, returns ALL arrays of that type.
392    ///
393    /// Examples:
394    /// - `find_typed_arrays("GParamSpec", true, Some("N_PROPS"))` finds
395    ///   `GParamSpec *props[N_PROPS]`
396    /// - `find_typed_arrays("GParamSpec", true, None)` finds ALL `GParamSpec
397    ///   *[]` arrays
398    /// - `find_typed_arrays("guint", false, Some("N_SIGNALS"))` finds `guint
399    ///   signals[N_SIGNALS]`
400    pub fn find_typed_arrays(
401        &self,
402        base_type: &str,
403        is_pointer: bool,
404        sentinel_name: Option<&str>,
405    ) -> Vec<&VariableDecl> {
406        self.iter_all_items()
407            .filter_map(|item| {
408                let TopLevelItem::Declaration(decl) = item else {
409                    return None;
410                };
411                if !decl.type_info.is_base_type(base_type)
412                    || decl.type_info.is_pointer() != is_pointer
413                {
414                    return None;
415                }
416                let matches = match &decl.array_size {
417                    Some(Expression::Identifier(size_id)) => {
418                        sentinel_name.is_none_or(|s| size_id.name == s)
419                    }
420                    Some(Expression::Binary(_)) => sentinel_name.is_none(),
421                    Some(_) => sentinel_name.is_none(),
422                    None => false,
423                };
424                matches.then_some(decl.as_ref())
425            })
426            .collect()
427    }
428
429    /// Find the GObjectType whose properties match the given property enum.
430    pub fn find_gobject_type_for_property_enum(
431        &self,
432        enum_info: &EnumInfo,
433    ) -> Option<&GObjectType> {
434        let property_names: Vec<&str> = enum_info
435            .values
436            .iter()
437            .filter(|v| !v.is_prop_0() && !v.is_prop_last())
438            .map(|v| v.name.as_str())
439            .collect();
440
441        let n_props_name = enum_info
442            .values
443            .last()
444            .filter(|v| v.is_prop_last())
445            .map(|v| v.name.as_str());
446
447        self.iter_all_gobject_types().find(|gt| {
448            // Match by property enum values in assignments
449            let has_matching_property = gt.properties.iter().any(|a| {
450                a.get_installed_enum_value()
451                    .is_some_and(|ev| property_names.contains(&ev))
452            });
453            if has_matching_property {
454                return true;
455            }
456
457            // Match by N_PROPS sentinel used in GParamSpec arrays referenced
458            // from this GObjectType's class_init
459            if let Some(sentinel) = n_props_name {
460                let class_init_name = gt.class_init_function_name();
461                if let Some(func) = self
462                    .iter_function_definitions()
463                    .find(|f| f.name == class_init_name)
464                {
465                    let install_calls = func.find_install_properties_calls();
466                    return install_calls.iter().any(|call| {
467                        call.get_arg(1)
468                            .and_then(|arg| arg.location().as_str())
469                            .is_some_and(|name| name == sentinel)
470                    });
471                }
472            }
473
474            false
475        })
476    }
477
478    /// Extract all vfunc assignments from a class_init function body.
479    /// Returns a map of (class_type, field) → func_name, e.g.
480    /// `("GObjectClass", "dispose") → "my_obj_dispose"`.
481    ///
482    /// For `klass->field = func`, the class type is resolved from the
483    /// class_init parameter type. For `G_OBJECT_CLASS(klass)->field = func`,
484    /// it is derived from the cast macro name.
485    pub fn resolve_class_init_vfuncs<'a>(
486        &'a self,
487        gobject_type: &GObjectType,
488    ) -> HashMap<(String, &'a str), &'a str> {
489        let class_init_name = gobject_type.class_init_function_name();
490        let Some(class_init) = self
491            .iter_function_definitions()
492            .find(|f| f.name == class_init_name)
493        else {
494            return HashMap::new();
495        };
496
497        let mut var_types: HashMap<&str, String> = HashMap::new();
498
499        if let Some(Parameter::Regular {
500            name: Some(name),
501            type_info,
502            ..
503        }) = class_init.parameters.first()
504        {
505            var_types.insert(name.as_str(), type_info.base_type.clone());
506        }
507
508        for stmt in &class_init.body_statements {
509            for decl in stmt.iter_declarations() {
510                if let Some(init) = &decl.initializer
511                    && let Expression::Call(call) = init
512                {
513                    let name = call.function_name();
514                    if let Some(prefix) = name
515                        .strip_suffix("_CLASS")
516                        .or_else(|| name.strip_suffix("_GET_CLASS"))
517                    {
518                        var_types.insert(
519                            decl.name.as_str(),
520                            format!("{}Class", prefix.to_upper_camel_case()),
521                        );
522                        continue;
523                    }
524                }
525                var_types.insert(decl.name.as_str(), decl.type_info.base_type.clone());
526            }
527        }
528
529        let func_names: HashSet<&str> = self
530            .iter_function_definitions()
531            .map(|f| f.name.as_str())
532            .collect();
533
534        let mut vfuncs = HashMap::new();
535        for assignment in class_init
536            .body_statements
537            .iter()
538            .flat_map(Statement::iter_assignments)
539        {
540            if let Expression::FieldAccess(fa) = &*assignment.lhs
541                && let Expression::Identifier(ident) = assignment.rhs.as_ref()
542                && func_names.contains(ident.name.as_str())
543            {
544                let class_type = match &*fa.base {
545                    Expression::Identifier(id) => var_types.get(id.name.as_str()).cloned(),
546                    Expression::Call(call) => {
547                        let name = call.function_name();
548                        let prefix = name
549                            .strip_suffix("_CLASS")
550                            .or_else(|| name.strip_suffix("_GET_CLASS"));
551                        prefix.map(|p| format!("{}Class", p.to_upper_camel_case()))
552                    }
553                    _ => None,
554                };
555                if let Some(ct) = class_type {
556                    vfuncs.insert((ct, fa.field.as_str()), ident.name.as_str());
557                }
558            }
559        }
560        vfuncs
561    }
562
563    pub fn resolve_property_enum_context(
564        &self,
565        enum_info: &EnumInfo,
566    ) -> Option<PropertyEnumContext<'_>> {
567        let gobject_type = self.find_gobject_type_for_property_enum(enum_info)?;
568        let class_init_name = gobject_type.class_init_function_name();
569        let class_init = self
570            .iter_function_definitions()
571            .find(|f| f.name == class_init_name)?;
572
573        let class_type_info = class_init.parameters.first().and_then(|p| {
574            if let Parameter::Regular { type_info, .. } = p {
575                Some(type_info)
576            } else {
577                None
578            }
579        });
580
581        let vfuncs = self.resolve_class_init_vfuncs(gobject_type);
582        let get_property_func = vfuncs
583            .iter()
584            .find(|((_, field), _)| *field == "get_property")
585            .map(|(_, func)| *func);
586        let set_property_func = vfuncs
587            .iter()
588            .find(|((_, field), _)| *field == "set_property")
589            .map(|(_, func)| *func);
590
591        Some(PropertyEnumContext {
592            gobject_type,
593            class_init,
594            class_type_info,
595            get_property_func,
596            set_property_func,
597        })
598    }
599
600    /// Iterate through all top-level items recursively (including items inside
601    /// `#ifdef`/`#if` and `G_BEGIN_DECLS` blocks). Conditional container items
602    /// themselves are also yielded before their children.
603    pub fn iter_all_items(&self) -> impl Iterator<Item = &TopLevelItem> + '_ {
604        self.iter_items_recursive(&self.top_level_items)
605    }
606
607    pub fn has_declarations(&self) -> bool {
608        self.iter_all_items().any(|item| {
609            matches!(
610                item,
611                TopLevelItem::TypeDefinition(_)
612                    | TopLevelItem::FunctionDeclaration(_)
613                    | TopLevelItem::FunctionDefinition(_)
614                    | TopLevelItem::Declaration(_)
615            )
616        })
617    }
618
619    /// Iterate typedef forward-alias declarations of the form
620    /// `typedef [struct|union] _Foo Foo` (i.e., typedefs that have no inline
621    /// struct body). Yields `(typedef_name, target_TypeInfo)` so callers can
622    /// inspect `target_type.base_type`, `.is_struct`, and `.is_union`.
623    pub fn iter_typedef_pairs(&self) -> impl Iterator<Item = (&str, &TypeInfo)> + '_ {
624        self.iter_all_items().filter_map(|item| match item {
625            TopLevelItem::TypeDefinition(TypeDefItem::Typedef {
626                name,
627                target: TypedefTarget::Type(target_type),
628                struct_fields,
629                ..
630            }) if struct_fields.is_empty() && !target_type.base_type.is_empty() => {
631                Some((name.as_str(), target_type))
632            }
633            _ => None,
634        })
635    }
636
637    /// Populate `properties` and `signals` on each `GObjectType` by finding
638    /// the matching `*_class_init` function and extracting param_spec
639    /// assignments and signal registrations from it.
640    pub fn resolve_gobject_types(&mut self, defines: &HashMap<String, DefineValue>) {
641        self.extract_manual_declare_types();
642        Self::extract_manual_gobject_types(&mut self.top_level_items);
643        Self::resolve_items(&mut self.top_level_items, defines);
644    }
645
646    /// Scan for manual `#define TYPE_MACRO (prefix_get_type ())` patterns
647    /// and synthesize Declare-style `GObjectType` entries. This covers headers
648    /// that don't use `G_DECLARE_*` macros.
649    fn extract_manual_declare_types(&mut self) {
650        let existing_macros: Vec<GType> = self
651            .iter_all_items()
652            .filter_map(|item| match item {
653                TopLevelItem::Preprocessor(PreprocessorDirective::GObjectType(gt)) => {
654                    gt.type_macro.clone()
655                }
656                _ => None,
657            })
658            .collect();
659
660        // Collect all defines for cross-referencing
661        struct DefineInfo {
662            name: String,
663            value: DefineValue,
664            location: SourceLocation,
665        }
666        let defines: Vec<DefineInfo> = self
667            .iter_all_items()
668            .filter_map(|item| match item {
669                TopLevelItem::Preprocessor(PreprocessorDirective::Define {
670                    name,
671                    value: Some(value),
672                    location,
673                }) => Some(DefineInfo {
674                    name: name.clone(),
675                    value: value.clone(),
676                    location: location.clone(),
677                }),
678                _ => None,
679            })
680            .collect();
681
682        let mut new_types = Vec::new();
683
684        for define in &defines {
685            // Match: #define XXX_TYPE_YYY (zzz_get_type ())
686            if !define.name.contains("_TYPE_") {
687                continue;
688            }
689            let gtype = GType::Identifier(define.name.clone());
690            if existing_macros.contains(&gtype) {
691                continue;
692            }
693
694            // Extract function prefix from the value
695            let raw = define.value.as_raw_str();
696            let trimmed = raw.trim().trim_matches(|c| c == '(' || c == ')');
697            let Some(func_name) = trimmed.split_whitespace().next() else {
698                continue;
699            };
700            let Some(function_prefix) = func_name.strip_suffix("_get_type") else {
701                continue;
702            };
703
704            // Find the cast macro to extract the type_name:
705            // #define XXX_YYY(obj) (G_TYPE_CHECK_INSTANCE_CAST (..., TypeName))
706            // The cast macro name is the type macro without _TYPE
707            let Some((module_prefix, type_suffix)) = define.name.split_once("_TYPE_") else {
708                continue;
709            };
710            let cast_macro_name = format!("{}_{}", module_prefix, type_suffix);
711
712            let type_name = defines
713                .iter()
714                .find(|d| d.name == cast_macro_name)
715                .and_then(|d| {
716                    // Last identifier before the closing paren is the type name
717                    // e.g. (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_APP_CHOOSER,
718                    // GtkAppChooser))
719                    d.value
720                        .as_raw_str()
721                        .rsplit(',')
722                        .next()
723                        .map(|s| s.trim().trim_end_matches(')').trim().to_owned())
724                });
725
726            let Some(type_name) = type_name else {
727                continue;
728            };
729            if type_name.is_empty() || !type_name.chars().next().unwrap().is_uppercase() {
730                continue;
731            }
732
733            // Check this type_name isn't already registered
734            let already_exists = self.iter_all_items().any(|item| {
735                matches!(
736                    item,
737                    TopLevelItem::Preprocessor(PreprocessorDirective::GObjectType(gt))
738                    if gt.type_name == type_name
739                )
740            });
741            if already_exists {
742                continue;
743            }
744
745            new_types.push(TopLevelItem::Preprocessor(
746                PreprocessorDirective::GObjectType(Box::new(GObjectType {
747                    type_name,
748                    type_macro: Some(gtype),
749                    function_prefix: function_prefix.to_owned(),
750                    parent_type: None,
751                    flags: None,
752                    kind: GObjectTypeKind::Declare {
753                        kind: DeclareKind::Derivable,
754                        module_prefix: module_prefix.to_owned(),
755                        type_prefix: type_suffix.to_owned(),
756                    },
757                    interfaces: Vec::new(),
758                    has_private: false,
759                    manually_registered: true,
760                    code_block_statements: Vec::new(),
761                    export_macros: Vec::new(),
762                    doc: None,
763                    properties: Vec::new(),
764                    signals: Vec::new(),
765                    location: define.location.clone(),
766                })),
767            ));
768        }
769
770        self.top_level_items.extend(new_types);
771    }
772
773    /// Scan for `*_get_type` functions that return `GType` and contain a
774    /// `g_type_register_static` or `g_type_register_static_simple` call.
775    /// Synthesize `GObjectType` entries for manual type registrations that
776    /// don't use `G_DEFINE_*` macros.
777    fn extract_manual_gobject_types(items: &mut Vec<TopLevelItem>) {
778        let existing_type_names: Vec<String> = items
779            .iter()
780            .filter_map(|item| match item {
781                TopLevelItem::Preprocessor(PreprocessorDirective::GObjectType(gt)) => {
782                    Some(gt.type_name.clone())
783                }
784                _ => None,
785            })
786            .collect();
787
788        let mut new_types = Vec::new();
789
790        for item in items.iter() {
791            let TopLevelItem::FunctionDefinition(func) = item else {
792                continue;
793            };
794            if !func.name.ends_with("_get_type") || func.return_type.base_type != "GType" {
795                continue;
796            }
797
798            let function_prefix = func.name.strip_suffix("_get_type").unwrap();
799
800            let register_call = func.find_calls_matching(|name| {
801                name == "g_type_register_static" || name == "g_type_register_static_simple"
802            });
803            let Some(call) = register_call.first() else {
804                continue;
805            };
806
807            // arg[0] = parent_type, arg[1] = "TypeName"
808            let Some(parent_expr) = call.get_arg(0) else {
809                continue;
810            };
811            let parent_type = match parent_expr {
812                Expression::Identifier(id) => id.name.clone(),
813                _ => continue,
814            };
815
816            let Some(type_name) = call.extract_string_from_arg(1) else {
817                continue;
818            };
819            let type_name = type_name.trim_matches('"').to_owned();
820
821            if existing_type_names.contains(&type_name) {
822                continue;
823            }
824
825            let flags = call
826                .get_arg(if call.arguments.len() > 4 { 6 } else { 3 })
827                .and_then(|e| match e {
828                    Expression::Identifier(id) => Some(id.name.as_str()),
829                    _ => None,
830                });
831
832            let is_interface = parent_type == "G_TYPE_INTERFACE";
833            let is_abstract = flags.is_some_and(|f| f.contains("ABSTRACT"));
834
835            let has_private = !func.find_calls(&["g_type_add_instance_private"]).is_empty();
836
837            let kind = if is_interface {
838                GObjectTypeKind::Define(DefineKind::Interface)
839            } else if is_abstract {
840                GObjectTypeKind::Define(DefineKind::AbstractType)
841            } else {
842                GObjectTypeKind::Define(DefineKind::Type)
843            };
844
845            let iface_calls = func.find_calls(&["g_type_add_interface_static"]);
846            let mut interfaces = Vec::new();
847            for iface_call in &iface_calls {
848                let Some(iface_type_expr) = iface_call.get_arg(1) else {
849                    continue;
850                };
851                let Expression::Identifier(iface_id) = iface_type_expr else {
852                    continue;
853                };
854                let interface_type = GType::Identifier(iface_id.name.clone());
855
856                // arg[2] is &info_var — extract var name, find its
857                // GInterfaceInfo decl, get init func from initializer[0]
858                let init_function = iface_call
859                    .get_arg(2)
860                    .and_then(|e| match e {
861                        Expression::Unary(u) if u.operator == UnaryOp::AddressOf => {
862                            match u.operand.as_ref() {
863                                Expression::Identifier(id) => Some(id.name.as_str()),
864                                _ => None,
865                            }
866                        }
867                        _ => None,
868                    })
869                    .and_then(|var_name| {
870                        Self::find_init_func_from_iface_info(&func.body_statements, var_name)
871                    });
872
873                interfaces.push(InterfaceImplementation {
874                    interface_type,
875                    init_function,
876                });
877            }
878
879            new_types.push(TopLevelItem::Preprocessor(
880                PreprocessorDirective::GObjectType(Box::new(GObjectType {
881                    type_name,
882                    type_macro: None,
883                    function_prefix: function_prefix.to_owned(),
884                    parent_type: Some(parent_type),
885                    flags: flags.map(std::borrow::ToOwned::to_owned),
886                    kind,
887                    interfaces,
888                    has_private,
889                    manually_registered: true,
890                    code_block_statements: Vec::new(),
891                    export_macros: Vec::new(),
892                    doc: None,
893                    properties: Vec::new(),
894                    signals: Vec::new(),
895                    location: func.location.clone(),
896                })),
897            ));
898        }
899
900        items.extend(new_types);
901    }
902
903    /// Find the init function name from a `GInterfaceInfo` variable
904    /// declaration. The init function is the first non-comment item in the
905    /// initializer list, possibly wrapped in a cast like
906    /// `(GInterfaceInitFunc) func_name`.
907    fn find_init_func_from_iface_info(stmts: &[Statement], var_name: &str) -> Option<String> {
908        for stmt in stmts {
909            match stmt {
910                Statement::Declaration(decl) if decl.name == var_name => {
911                    let Expression::InitializerList(init) = decl.initializer.as_ref()? else {
912                        return None;
913                    };
914                    let first = init
915                        .items
916                        .iter()
917                        .find(|item| !matches!(&*item.value, Expression::Comment(_)))?;
918                    return Some(Self::unwrap_cast_to_identifier(&first.value)?.to_owned());
919                }
920                Statement::If(if_stmt) => {
921                    if let Some(v) =
922                        Self::find_init_func_from_iface_info(&if_stmt.then_body, var_name)
923                    {
924                        return Some(v);
925                    }
926                    if let Some(else_body) = &if_stmt.else_body
927                        && let Some(v) = Self::find_init_func_from_iface_info(else_body, var_name)
928                    {
929                        return Some(v);
930                    }
931                }
932                Statement::Compound(c) => {
933                    if let Some(v) = Self::find_init_func_from_iface_info(&c.statements, var_name) {
934                        return Some(v);
935                    }
936                }
937                _ => {}
938            }
939        }
940        None
941    }
942
943    /// Unwrap `(SomeType) identifier` casts to get the inner identifier name.
944    fn unwrap_cast_to_identifier(expr: &Expression) -> Option<&str> {
945        match expr {
946            Expression::Identifier(id) => Some(&id.name),
947            Expression::Cast(c) => Self::unwrap_cast_to_identifier(&c.operand),
948            _ => None,
949        }
950    }
951
952    fn resolve_items(items: &mut [TopLevelItem], defines: &HashMap<String, DefineValue>) {
953        for i in 0..items.len() {
954            match &items[i] {
955                TopLevelItem::Preprocessor(PreprocessorDirective::GObjectType(gt)) => {
956                    let type_name = gt.type_name.clone();
957
958                    let init_names = if gt.is_interface() {
959                        vec![
960                            gt.default_init_function_name(),
961                            gt.class_init_function_name(),
962                        ]
963                    } else {
964                        vec![gt.class_init_function_name()]
965                    };
966
967                    let func_idx = items.iter().position(|item| {
968                        matches!(item, TopLevelItem::FunctionDefinition(f) if init_names.contains(&f.name))
969                    });
970                    if let Some(j) = func_idx {
971                        let func = match &items[j] {
972                            TopLevelItem::FunctionDefinition(f) => f,
973                            _ => unreachable!(),
974                        };
975
976                        let properties = func.find_param_spec_assignments(&type_name, defines);
977                        let signals = func.find_signal_registrations(&type_name);
978                        if let TopLevelItem::Preprocessor(PreprocessorDirective::GObjectType(gt)) =
979                            &mut items[i]
980                        {
981                            gt.properties = properties;
982                            gt.signals = signals;
983                        }
984                    }
985
986                    if let Some(doc) = Self::find_type_doc_in_comments(items, &type_name)
987                        && let TopLevelItem::Preprocessor(PreprocessorDirective::GObjectType(gt)) =
988                            &mut items[i]
989                    {
990                        gt.doc = Some(doc);
991                    }
992                }
993                TopLevelItem::Preprocessor(PreprocessorDirective::Conditional { .. })
994                | TopLevelItem::Preprocessor(PreprocessorDirective::GObjectDeclsBlock { .. }) => {}
995                _ => {}
996            }
997        }
998
999        Self::resolve_type_docs(items);
1000
1001        for item in items.iter_mut() {
1002            match item {
1003                TopLevelItem::Preprocessor(PreprocessorDirective::Conditional { body, .. })
1004                | TopLevelItem::Preprocessor(PreprocessorDirective::GObjectDeclsBlock {
1005                    body,
1006                    ..
1007                }) => {
1008                    Self::resolve_items(body, defines);
1009                }
1010                _ => {}
1011            }
1012        }
1013    }
1014
1015    fn find_type_doc_in_comments(items: &[TopLevelItem], type_name: &str) -> Option<TypeDoc> {
1016        items.iter().find_map(|item| {
1017            if let TopLevelItem::Comment(c) = item {
1018                let doc = TypeDoc::from_comment(c)?;
1019                let sym = doc.symbol.as_deref()?;
1020                if sym == type_name {
1021                    return Some(doc);
1022                }
1023            }
1024            None
1025        })
1026    }
1027
1028    fn resolve_type_docs(items: &mut [TopLevelItem]) {
1029        for i in 0..items.len() {
1030            let name = match &items[i] {
1031                TopLevelItem::TypeDefinition(
1032                    TypeDefItem::Struct {
1033                        name, doc: None, ..
1034                    }
1035                    | TypeDefItem::Typedef {
1036                        name, doc: None, ..
1037                    },
1038                ) => Some(name.trim_start_matches('_').to_owned()),
1039                TopLevelItem::TypeDefinition(TypeDefItem::Enum(e)) if e.doc.is_none() => {
1040                    e.name.clone()
1041                }
1042                _ => None,
1043            };
1044            if let Some(name) = name
1045                && let Some(doc) = Self::find_type_doc_in_comments(items, &name)
1046            {
1047                match &mut items[i] {
1048                    TopLevelItem::TypeDefinition(
1049                        TypeDefItem::Struct { doc: d, .. } | TypeDefItem::Typedef { doc: d, .. },
1050                    ) => {
1051                        *d = Some(doc);
1052                    }
1053                    TopLevelItem::TypeDefinition(TypeDefItem::Enum(e)) => {
1054                        e.doc = Some(doc);
1055                    }
1056                    _ => {}
1057                }
1058            }
1059        }
1060
1061        Self::resolve_enum_value_docs(items);
1062    }
1063
1064    fn resolve_enum_value_docs(items: &mut [TopLevelItem]) {
1065        for i in 0..items.len() {
1066            let TopLevelItem::TypeDefinition(TypeDefItem::Enum(e)) = &mut items[i] else {
1067                continue;
1068            };
1069
1070            // Extract inline @VALUE: entries from the parent enum's doc comment
1071            let enum_name = match &e.name {
1072                Some(n) => n.clone(),
1073                None => continue,
1074            };
1075            let inline_docs: Vec<(String, EnumValueDoc)> = items
1076                .iter()
1077                .find_map(|item| {
1078                    if let TopLevelItem::Comment(c) = item {
1079                        let doc = TypeDoc::from_comment(c)?;
1080                        if doc.symbol.as_deref() == Some(&enum_name) {
1081                            return Some(EnumValueDoc::extract_inline_from_comment(c));
1082                        }
1083                    }
1084                    None
1085                })
1086                .unwrap_or_default();
1087
1088            // Re-borrow mutably after the immutable iteration
1089            let TopLevelItem::TypeDefinition(TypeDefItem::Enum(e)) = &mut items[i] else {
1090                continue;
1091            };
1092
1093            for value in &mut e.values {
1094                // First try inline @VALUE: from parent doc
1095                if let Some((_, doc)) = inline_docs.iter().find(|(n, _)| *n == value.name) {
1096                    value.doc = Some(doc.clone());
1097                }
1098            }
1099
1100            // Then for values still without docs, look for standalone comments
1101            let missing: Vec<String> = e
1102                .values
1103                .iter()
1104                .filter(|v| v.doc.is_none())
1105                .map(|v| v.name.clone())
1106                .collect();
1107
1108            let standalone_docs: Vec<(String, EnumValueDoc)> = missing
1109                .iter()
1110                .filter_map(|name| {
1111                    let doc = items.iter().find_map(|item| {
1112                        if let TopLevelItem::Comment(c) = item {
1113                            let doc = EnumValueDoc::from_comment(c)?;
1114                            if doc.symbol.as_deref() == Some(name.as_str()) {
1115                                return Some(doc);
1116                            }
1117                        }
1118                        None
1119                    })?;
1120                    Some((name.clone(), doc))
1121                })
1122                .collect();
1123
1124            let TopLevelItem::TypeDefinition(TypeDefItem::Enum(e)) = &mut items[i] else {
1125                continue;
1126            };
1127            for (name, doc) in standalone_docs {
1128                if let Some(value) = e.values.iter_mut().find(|v| v.name == name) {
1129                    value.doc = Some(doc);
1130                }
1131            }
1132        }
1133    }
1134
1135    /// Recursively iterate through all items (including those in #ifdef blocks)
1136    fn iter_items_recursive<'a>(
1137        &'a self,
1138        items: &'a [TopLevelItem],
1139    ) -> Box<dyn Iterator<Item = &'a TopLevelItem> + 'a> {
1140        Box::new(items.iter().flat_map(move |item| match item {
1141            TopLevelItem::Preprocessor(PreprocessorDirective::Conditional { body, .. })
1142            | TopLevelItem::Preprocessor(PreprocessorDirective::GObjectDeclsBlock {
1143                body, ..
1144            }) => Box::new(std::iter::once(item).chain(self.iter_items_recursive(body)))
1145                as Box<dyn Iterator<Item = &'a TopLevelItem>>,
1146            _ => Box::new(std::iter::once(item)) as Box<dyn Iterator<Item = &'a TopLevelItem>>,
1147        }))
1148    }
1149
1150    fn iter_items_with_parent<'a>(
1151        &'a self,
1152        items: &'a [TopLevelItem],
1153        parent: Option<&'a TopLevelItem>,
1154    ) -> Box<dyn Iterator<Item = (&'a TopLevelItem, Option<&'a TopLevelItem>)> + 'a> {
1155        Box::new(items.iter().flat_map(move |item| {
1156            match item {
1157                TopLevelItem::Preprocessor(PreprocessorDirective::Conditional { body, .. })
1158                | TopLevelItem::Preprocessor(PreprocessorDirective::GObjectDeclsBlock {
1159                    body,
1160                    ..
1161                }) => Box::new(
1162                    std::iter::once((item, parent))
1163                        .chain(self.iter_items_with_parent(body, Some(item))),
1164                )
1165                    as Box<dyn Iterator<Item = (&'a TopLevelItem, Option<&'a TopLevelItem>)>>,
1166                _ => Box::new(std::iter::once((item, parent)))
1167                    as Box<dyn Iterator<Item = (&'a TopLevelItem, Option<&'a TopLevelItem>)>>,
1168            }
1169        }))
1170    }
1171
1172    pub fn iter_all_items_with_parent(
1173        &self,
1174    ) -> impl Iterator<Item = (&TopLevelItem, Option<&TopLevelItem>)> + '_ {
1175        self.iter_items_with_parent(&self.top_level_items, None)
1176    }
1177}