Skip to main content

gobject_ast/model/types/
function.rs

1use std::collections::HashMap;
2
3use serde::Serialize;
4
5use crate::model::{
6    CallExpression, DefineValue, ExportMacro, Expression, FunctionDoc, ParamSpecAssignment,
7    Property, PropertyDoc, Signal, SignalDoc, SourceLocation, Statement, TypeInfo, VariableDecl,
8};
9
10#[derive(Debug, Clone, Serialize)]
11#[serde(untagged)]
12pub enum Parameter {
13    Regular {
14        #[serde(skip_serializing_if = "Option::is_none")]
15        name: Option<String>,
16        type_info: TypeInfo,
17        location: SourceLocation,
18    },
19    Variadic,
20}
21
22#[derive(Debug, Clone, Serialize)]
23pub struct FunctionDeclItem {
24    pub name: String,
25    pub return_type: TypeInfo,
26    #[serde(skip_serializing_if = "std::ops::Not::not")]
27    pub is_static: bool,
28    #[serde(skip_serializing_if = "std::ops::Not::not")]
29    pub is_inline: bool,
30    #[serde(skip_serializing_if = "Vec::is_empty")]
31    pub parameters: Vec<Parameter>,
32    #[serde(skip_serializing_if = "Vec::is_empty")]
33    pub export_macros: Vec<ExportMacro>,
34    pub location: SourceLocation,
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub doc: Option<FunctionDoc>,
37}
38
39#[derive(Debug, Clone, Serialize)]
40pub struct FunctionDefItem {
41    pub name: String,
42    pub return_type: TypeInfo,
43    #[serde(skip_serializing_if = "std::ops::Not::not")]
44    pub is_static: bool,
45    #[serde(skip_serializing_if = "std::ops::Not::not")]
46    pub is_inline: bool,
47    #[serde(skip_serializing_if = "Vec::is_empty")]
48    pub parameters: Vec<Parameter>,
49    #[serde(skip_serializing_if = "Vec::is_empty")]
50    pub body_statements: Vec<Statement>,
51    pub location: SourceLocation,
52    #[serde(skip)]
53    pub body_location: Option<SourceLocation>,
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub doc: Option<FunctionDoc>,
56}
57
58impl FunctionDefItem {
59    /// Find all calls to specific functions in the body
60    /// Returns references to all CallExpression nodes that match any of the
61    /// given function names
62    pub fn find_calls<'a>(&'a self, function_names: &[&str]) -> Vec<&'a CallExpression> {
63        self.find_calls_matching(|name| function_names.contains(&name))
64    }
65
66    /// Find all calls matching a predicate in the body
67    pub fn find_calls_matching<F>(&self, predicate: F) -> Vec<&CallExpression>
68    where
69        F: Fn(&str) -> bool,
70    {
71        let mut exprs: Vec<&Expression> = Vec::new();
72        for stmt in &self.body_statements {
73            stmt.walk_expressions(&mut |expr| exprs.push(expr));
74        }
75
76        let mut results = Vec::new();
77        for expr in exprs {
78            expr.walk(&mut |e| {
79                if let Expression::Call(call) = e
80                    && call.function_name_str().is_some_and(&predicate)
81                {
82                    results.push(call);
83                }
84            });
85        }
86        results
87    }
88
89    /// Extract signal registrations from the function body.
90    /// Populates `enum_value` when the signal is assigned via
91    /// `signals[ENUM] = g_signal_new(...)`.
92    pub fn find_signal_registrations(&self, type_name: &str) -> Vec<Signal> {
93        let mut signals = Vec::new();
94        let mut seen_names = std::collections::HashSet::new();
95
96        // First pass: assignments like `signals[ENUM] = g_signal_new(...)`
97        for (i, stmt) in self.body_statements.iter().enumerate() {
98            for assignment in stmt.iter_assignments() {
99                let Expression::Call(call) = &*assignment.rhs else {
100                    continue;
101                };
102                if !call.function_contains("g_signal_new") {
103                    continue;
104                }
105                let Some(mut signal) = Signal::from_g_signal_new_call(call) else {
106                    continue;
107                };
108                if let Expression::Subscript(sub) = &*assignment.lhs
109                    && let Expression::Identifier(id) = &*sub.index
110                {
111                    signal.enum_value = Some(id.name.clone());
112                }
113                if i > 0
114                    && let Statement::Comment(c) = &self.body_statements[i - 1]
115                {
116                    signal.doc = SignalDoc::from_comment_for(c, type_name, &signal.name);
117                }
118                seen_names.insert(signal.name.clone());
119                signals.push(signal);
120            }
121        }
122
123        // Second pass: standalone g_signal_new calls not already captured
124        for (i, stmt) in self.body_statements.iter().enumerate() {
125            for call in stmt.iter_calls() {
126                if !call.function_name().starts_with("g_signal_new") {
127                    continue;
128                }
129                let Some(name) = call.extract_string_from_arg(0) else {
130                    continue;
131                };
132                if seen_names.contains(&name) {
133                    continue;
134                }
135                if let Some(mut signal) = Signal::from_g_signal_new_call(call) {
136                    if i > 0
137                        && let Statement::Comment(c) = &self.body_statements[i - 1]
138                    {
139                        signal.doc = SignalDoc::from_comment_for(c, type_name, &signal.name);
140                    }
141                    signals.push(signal);
142                }
143            }
144        }
145
146        signals
147    }
148
149    /// Iterate all local variable declarations in the function body recursively
150    pub fn iter_local_declarations(&self) -> impl Iterator<Item = &VariableDecl> {
151        self.body_statements
152            .iter()
153            .flat_map(Statement::iter_declarations)
154    }
155
156    /// Collect all return values from the function body
157    pub fn collect_return_values(&self) -> Vec<&Expression> {
158        self.body_statements
159            .iter()
160            .flat_map(Statement::iter_returns)
161            .filter_map(|r| r.value.as_ref())
162            .collect()
163    }
164
165    /// Check if any variable of the given type is directly returned from the
166    /// function
167    pub fn is_var_returned(&self, type_info: &TypeInfo) -> bool {
168        for stmt in &self.body_statements {
169            for ret in stmt.iter_returns() {
170                if let Some(Expression::Identifier(id)) = &ret.value {
171                    // Find the declaration of this identifier in all body statements
172                    for body_stmt in &self.body_statements {
173                        for decl in body_stmt.iter_declarations() {
174                            if decl.name == id.name
175                                && decl.type_info.base_type == type_info.base_type
176                                && decl.type_info.is_pointer() == type_info.is_pointer()
177                            {
178                                return true;
179                            }
180                        }
181                    }
182                }
183            }
184        }
185        false
186    }
187
188    /// Check if any variable of the given type is passed to a cleanup call
189    /// (g_object_unref, g_free, etc.)
190    pub fn is_var_passed_to_cleanup(&self, type_info: &TypeInfo) -> bool {
191        for stmt in &self.body_statements {
192            for call in stmt.iter_calls() {
193                if call.is_cleanup_call()
194                    && let Some(arg) = call.get_arg(0)
195                    && let Expression::Identifier(id) = arg
196                {
197                    // Find the declaration of this identifier
198                    for body_stmt in &self.body_statements {
199                        for decl in body_stmt.iter_declarations() {
200                            if decl.name == id.name
201                                && decl.type_info.base_type == type_info.base_type
202                                && decl.type_info.is_pointer() == type_info.is_pointer()
203                            {
204                                return true;
205                            }
206                        }
207                    }
208                }
209            }
210        }
211        false
212    }
213
214    /// Check if the named variable is passed to a specific function at a
215    /// specific argument position
216    pub fn is_var_passed_to_function(
217        &self,
218        var_name: &str,
219        func_name: &str,
220        arg_index: usize,
221    ) -> bool {
222        self.body_statements.iter().any(|stmt| {
223            stmt.iter_calls().any(|call| {
224                call.is_function(func_name)
225                    && call.get_arg(arg_index).is_some_and(
226                        |arg| matches!(arg, Expression::Identifier(id) if id.name == var_name),
227                    )
228            })
229        })
230    }
231
232    /// Check if any variable of the given type is allocated via an allocation
233    /// call Uses `call.is_allocation_call()` to detect allocations by
234    /// default
235    pub fn is_var_allocated(&self, type_info: &TypeInfo) -> bool {
236        self.is_var_allocated_with(type_info, CallExpression::is_allocation_call)
237    }
238
239    /// Check if any variable of the given type is allocated via a custom
240    /// allocation predicate
241    pub fn is_var_allocated_with(
242        &self,
243        type_info: &TypeInfo,
244        is_allocation: impl Fn(&CallExpression) -> bool,
245    ) -> bool {
246        for stmt in &self.body_statements {
247            let mut found = false;
248            stmt.walk(&mut |s| {
249                match s {
250                    // Check init: Type *var = allocation_call()
251                    Statement::Declaration(decl)
252                        if decl.type_info.base_type == type_info.base_type
253                            && decl.type_info.is_pointer() == type_info.is_pointer() =>
254                    {
255                        let is_alloc_init = match &decl.initializer {
256                            Some(Expression::Call(call)) => is_allocation(call),
257                            Some(Expression::AllocCall(_)) => true,
258                            _ => false,
259                        };
260                        if is_alloc_init {
261                            found = true;
262                        }
263                    }
264                    // Check assignment: var = allocation_call()
265                    Statement::Expression(expr_stmt) => {
266                        if let Expression::Assignment(assign) = expr_stmt.as_ref()
267                            && let Expression::Identifier(id) = &*assign.lhs
268                        {
269                            let is_alloc_rhs = match &*assign.rhs {
270                                Expression::Call(call) => is_allocation(call),
271                                Expression::AllocCall(_) => true,
272                                _ => false,
273                            };
274                            if is_alloc_rhs {
275                                // Find the declaration of the assigned variable
276                                for body_stmt in &self.body_statements {
277                                    for decl in body_stmt.iter_declarations() {
278                                        if decl.name == id.name
279                                            && decl.type_info.base_type == type_info.base_type
280                                            && decl.type_info.is_pointer() == type_info.is_pointer()
281                                        {
282                                            found = true;
283                                        }
284                                    }
285                                }
286                            }
287                        }
288                    }
289                    _ => {}
290                }
291            });
292            if found {
293                return true;
294            }
295        }
296        false
297    }
298
299    /// Find all g_object_class_install_properties calls in the function body
300    pub fn find_install_properties_calls(&self) -> Vec<&CallExpression> {
301        self.find_calls(&["g_object_class_install_properties"])
302    }
303
304    /// Map every named parameter and local variable to its `TypeInfo`.
305    /// Parameters appear first; local declarations in body order after that,
306    /// so an inner-scope shadowing declaration overwrites the outer one.
307    pub fn local_var_types(&self) -> std::collections::HashMap<&str, &TypeInfo> {
308        let mut map = std::collections::HashMap::new();
309        for param in &self.parameters {
310            if let Parameter::Regular {
311                name: Some(name),
312                type_info,
313                ..
314            } = param
315            {
316                map.insert(name.as_str(), type_info);
317            }
318        }
319        for stmt in &self.body_statements {
320            stmt.walk(&mut |s| {
321                if let Statement::Declaration(decl) = s {
322                    map.insert(decl.name.as_str(), &decl.type_info);
323                }
324            });
325        }
326        map
327    }
328
329    /// Get a parameter by name
330    pub fn get_param_by_name(&self, name: &str) -> Option<&Parameter> {
331        self.parameters
332            .iter()
333            .find(|p| matches!(p, Parameter::Regular { name: Some(n), .. } if n == name))
334    }
335
336    /// Find all param_spec assignments in the function body
337    /// Handles array pattern (props[PROP_X] = ...), variable pattern
338    /// (param_spec = ...), and override pattern
339    /// (g_object_class_override_property(...))
340    pub(crate) fn find_param_spec_assignments(
341        &self,
342        type_name: &str,
343        defines: &HashMap<String, DefineValue>,
344    ) -> Vec<ParamSpecAssignment> {
345        let mut assignments = Vec::new();
346        let mut array_assignments: HashMap<&str, Vec<usize>> = HashMap::new();
347        let mut variable_assignments: HashMap<&str, Vec<usize>> = HashMap::new();
348
349        // First pass: collect all assignments
350        for (i, stmt) in self.body_statements.iter().enumerate() {
351            stmt.walk(&mut |s| {
352                match s {
353                    // Declaration: GParamSpec *pspec = g_param_spec_*()
354                    Statement::Declaration(decl)
355                        if matches!(
356                            &decl.initializer,
357                            Some(Expression::Call(c)) if c.function_contains("_param_spec_")
358                        ) =>
359                    {
360                        let Some(Expression::Call(param_call)) = &decl.initializer else {
361                            unreachable!()
362                        };
363                        let Some(mut property) =
364                            Property::from_param_spec_call(param_call, defines)
365                        else {
366                            return;
367                        };
368                        if i > 0
369                            && let Statement::Comment(c) = &self.body_statements[i - 1]
370                        {
371                            property.doc =
372                                PropertyDoc::from_comment_for(c, type_name, &property.name);
373                        }
374                        let idx = assignments.len();
375                        variable_assignments
376                            .entry(&*decl.name)
377                            .or_default()
378                            .push(idx);
379                        assignments.push(ParamSpecAssignment::Variable {
380                            variable_name: decl.name.clone(),
381                            statement_location: s.location().clone(),
382                            call: param_call.clone(),
383                            property,
384                            install_call: None,
385                        });
386                    }
387                    Statement::Expression(expr_stmt) => {
388                        match expr_stmt.as_ref() {
389                            // Assignment: props[PROP_X] = g_param_spec_*() or spec =
390                            // g_param_spec_*()
391                            Expression::Assignment(assignment) => {
392                                if let Expression::Call(param_call) = &*assignment.rhs {
393                                    let func_name = param_call.function_name();
394                                    if !func_name.contains("_param_spec_") {
395                                        return;
396                                    }
397
398                                    let Some(mut property) =
399                                        Property::from_param_spec_call(param_call, defines)
400                                    else {
401                                        return;
402                                    };
403                                    if i > 0
404                                        && let Statement::Comment(c) = &self.body_statements[i - 1]
405                                    {
406                                        property.doc = PropertyDoc::from_comment_for(
407                                            c,
408                                            type_name,
409                                            &property.name,
410                                        );
411                                    }
412
413                                    // Check LHS: array subscript or variable?
414                                    if let Expression::Subscript(subscript) = &*assignment.lhs {
415                                        if let Some(array_name) =
416                                            subscript.array.location().as_str()
417                                            && let Some(enum_value) =
418                                                subscript.index.location().as_str()
419                                        {
420                                            let idx = assignments.len();
421                                            array_assignments
422                                                .entry(array_name)
423                                                .or_default()
424                                                .push(idx);
425                                            assignments.push(ParamSpecAssignment::ArraySubscript {
426                                                array_name: array_name.to_owned(),
427                                                enum_value: enum_value.to_owned(),
428                                                statement_location: s.location().clone(),
429                                                call: param_call.clone(),
430                                                property,
431                                                install_call: None,
432                                            });
433                                        }
434                                    } else if let Some(var_name) =
435                                        assignment.lhs.location().as_str()
436                                    {
437                                        let idx = assignments.len();
438                                        variable_assignments.entry(var_name).or_default().push(idx);
439                                        assignments.push(ParamSpecAssignment::Variable {
440                                            variable_name: var_name.to_owned(),
441                                            statement_location: s.location().clone(),
442                                            call: param_call.clone(),
443                                            property,
444                                            install_call: None,
445                                        });
446                                    }
447                                }
448                            }
449                            // Direct call: g_object_class_override_property(class, PROP_X, "name")
450                            Expression::Call(call) => {
451                                if call.function_contains("override_property")
452                                    && let Some(mut property) =
453                                        Property::from_override_property_call(call, defines)
454                                    && let Some(enum_arg) = call.get_arg(1)
455                                    && let Some(enum_value) = enum_arg.location().as_str()
456                                {
457                                    if i > 0
458                                        && let Statement::Comment(c) = &self.body_statements[i - 1]
459                                    {
460                                        property.doc = PropertyDoc::from_comment_for(
461                                            c,
462                                            type_name,
463                                            &property.name,
464                                        );
465                                    }
466                                    assignments.push(ParamSpecAssignment::OverrideProperty {
467                                        enum_value: enum_value.to_owned(),
468                                        statement_location: s.location().clone(),
469                                        call: call.clone(),
470                                        property,
471                                    });
472                                }
473                            }
474                            _ => {}
475                        }
476                    }
477                    _ => {}
478                }
479            });
480        }
481
482        // Second pass: find install calls and link them to assignments
483        for (i, stmt) in self.body_statements.iter().enumerate() {
484            stmt.walk(&mut |s| {
485                if let Statement::Expression(expr_stmt) = s
486                    && let Expression::Call(call) = expr_stmt.as_ref()
487                {
488                    // g_object_class_install_properties(class, N_PROPS, array)
489                    if call.function_contains("install_properties") {
490                        if let Some(array_arg) = call.get_arg(2)
491                            && let Some(array_name) = array_arg.location().as_str()
492                            && let Some(indices) = array_assignments.get(&array_name)
493                        {
494                            for &idx in indices {
495                                if let ParamSpecAssignment::ArraySubscript {
496                                    install_call, ..
497                                } = &mut assignments[idx]
498                                {
499                                    *install_call = Some(call.clone());
500                                }
501                            }
502                        }
503                    }
504                    // g_object_class_install_property(class, PROP_X, spec) — 3 args
505                    // g_object_interface_install_property(iface, spec) — 2 args
506                    else if call.function_contains("install_property") {
507                        let is_interface = call.function_contains("interface_install_property");
508                        let spec_arg_idx = if is_interface { 1 } else { 2 };
509
510                        if let Some(spec_expr) = call.get_arg(spec_arg_idx) {
511                            if let Expression::Call(spec_call) = spec_expr
512                                && spec_call.function_contains("_param_spec_")
513                                && let Some(mut property) =
514                                    Property::from_param_spec_call(spec_call, defines)
515                            {
516                                let enum_value = if is_interface {
517                                    String::new()
518                                } else if let Some(enum_arg) = call.get_arg(1)
519                                    && let Some(ev) = enum_arg.location().as_str()
520                                {
521                                    ev.to_owned()
522                                } else {
523                                    return;
524                                };
525
526                                if i > 0
527                                    && let Statement::Comment(c) = &self.body_statements[i - 1]
528                                {
529                                    property.doc = PropertyDoc::from_comment(c);
530                                }
531                                assignments.push(ParamSpecAssignment::DirectInstall {
532                                    enum_value,
533                                    statement_location: s.location().clone(),
534                                    call: spec_call.clone(),
535                                    property,
536                                    install_call: call.clone(),
537                                });
538                            } else if let Some(var_name) = spec_expr.location().as_str()
539                                && let Some(indices) = variable_assignments.get(var_name)
540                            {
541                                let indices = indices.clone();
542                                for idx in indices {
543                                    if let ParamSpecAssignment::Variable { install_call, .. } =
544                                        &mut assignments[idx]
545                                        && install_call.is_none()
546                                    {
547                                        *install_call = Some(call.clone());
548                                        break;
549                                    }
550                                }
551                            }
552                        }
553                    }
554                }
555            });
556        }
557
558        assignments.sort_by_key(|a| a.statement_location().start_byte);
559        assignments
560    }
561}