Skip to main content

gobject_ast/model/expression/
call.rs

1use serde::Serialize;
2
3use crate::model::{Expression, SourceLocation};
4
5#[derive(Debug, Clone, Serialize)]
6pub struct CallExpression {
7    pub function: Box<Expression>,
8    #[serde(skip_serializing_if = "Vec::is_empty")]
9    pub arguments: Vec<Box<Expression>>,
10    pub location: SourceLocation,
11}
12
13impl CallExpression {
14    /// Get the function name as a string
15    /// For identifiers, returns the name
16    /// For field access, returns the full text (e.g., "parent_class->dispose")
17    pub fn function_name(&self) -> &str {
18        self.function.location().as_str().unwrap_or("")
19    }
20
21    /// Check if the function matches a specific name
22    pub fn is_function(&self, name: &str) -> bool {
23        match &*self.function {
24            Expression::Identifier(id) => id.name == name,
25            _ => false,
26        }
27    }
28
29    /// Check if function name contains a pattern
30    pub fn function_contains(&self, pattern: &str) -> bool {
31        self.function_name().contains(pattern)
32    }
33
34    /// Check if function name ends with a pattern
35    pub fn function_ends_with(&self, pattern: &str) -> bool {
36        self.function_name().ends_with(pattern)
37    }
38
39    /// Get the function name as &str (for common case of identifier)
40    /// Returns None if function is not a simple identifier
41    pub fn function_name_str(&self) -> Option<&str> {
42        match &*self.function {
43            Expression::Identifier(id) => Some(&id.name),
44            _ => None,
45        }
46    }
47
48    /// Get the expression for the argument at the given index
49    pub fn get_arg(&self, index: usize) -> Option<&Expression> {
50        self.arguments.get(index).map(std::convert::AsRef::as_ref)
51    }
52
53    /// Get argument as source text
54    pub fn get_arg_text(&self, index: usize) -> Option<&str> {
55        self.arguments.get(index)?.location().as_str()
56    }
57
58    /// Check if the argument at the given index exists and matches the
59    /// predicate
60    pub fn has_arg_matching<F>(&self, index: usize, predicate: F) -> bool
61    where
62        F: FnOnce(&Expression) -> bool,
63    {
64        self.get_arg(index).is_some_and(predicate)
65    }
66
67    /// Check if the argument at the given index contains a reference to the
68    /// specified variable Handles both plain identifiers and field access
69    /// (e.g., obj->field)
70    pub fn arg_contains_variable(&self, index: usize, var_name: &str) -> bool {
71        self.has_arg_matching(index, |expr| {
72            expr.extract_variable_name()
73                .is_some_and(|name| name == var_name)
74        })
75    }
76
77    /// Check if this looks like a macro call (ALL_CAPS or ends with _)
78    /// Examples: I_, N_, G_STRINGIFY, GINT_TO_POINTER
79    pub fn is_likely_macro(&self) -> bool {
80        let name = self.function_name();
81        name.chars().all(|c| c.is_uppercase() || c == '_') || name.ends_with('_')
82    }
83
84    /// Extract string literal from argument, unwrapping macro calls like
85    /// I_("string") This is useful for g_param_spec calls where the name
86    /// might be I_("property-name")
87    pub fn extract_string_from_arg(&self, index: usize) -> Option<String> {
88        self.arguments.get(index)?.extract_string_value()
89    }
90
91    /// Check if this call is a GObject allocation function
92    /// Recognizes g_object_new, g_new, and various other allocation patterns
93    pub fn is_allocation_call(&self) -> bool {
94        if let Some(name) = self.function_name_str() {
95            matches!(
96                name,
97                "g_object_new"
98                    | "g_object_new_with_properties"
99                    | "g_type_create_instance"
100                    | "g_new"
101                    | "g_new0"
102                    | "g_try_new"
103                    | "g_try_new0"
104                    | "g_malloc"
105                    | "g_malloc0"
106                    | "g_strdup"
107                    | "g_strndup"
108                    | "g_file_new_for_path"
109                    | "g_file_new_for_uri"
110                    | "g_file_new_tmp"
111                    | "g_variant_new"
112                    | "g_variant_ref_sink"
113                    | "g_bytes_new"
114                    | "g_bytes_new_take"
115                    | "g_hash_table_new"
116                    | "g_hash_table_new_full"
117                    | "g_array_new"
118                    | "g_ptr_array_new"
119                    | "g_error_new"
120                    | "g_error_new_literal"
121            ) || name.ends_with("_new")
122                || name.ends_with("_get_instance")
123                || name.contains("_new_")
124                || name.contains("_create")
125        } else {
126            false
127        }
128    }
129
130    /// Check if this call is a GObject cleanup/free function
131    /// Recognizes g_object_unref, g_free, and various other cleanup patterns
132    pub fn is_cleanup_call(&self) -> bool {
133        if let Some(name) = self.function_name_str() {
134            matches!(
135                name,
136                "g_object_unref"
137                    | "g_clear_object"
138                    | "g_clear_pointer"
139                    | "g_error_free"
140                    | "g_clear_error"
141                    | "g_free"
142                    | "g_clear_handle_id"
143                    | "g_clear_signal_handler"
144                    | "g_list_free"
145                    | "g_list_free_full"
146                    | "g_slist_free"
147                    | "g_slist_free_full"
148                    | "g_hash_table_unref"
149                    | "g_hash_table_destroy"
150                    | "g_bytes_unref"
151                    | "g_variant_unref"
152                    | "g_array_unref"
153                    | "g_array_free"
154                    | "g_ptr_array_unref"
155                    | "g_ptr_array_free"
156            ) || name.ends_with("_unref")
157                || name.ends_with("_free")
158                || name.ends_with("_destroy")
159        } else {
160            false
161        }
162    }
163}