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>, // Can be Identifier or FieldAccess
8    #[serde(skip_serializing_if = "Vec::is_empty")]
9    pub arguments: Vec<Argument>,
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<'a>(&self, source: &'a [u8]) -> &'a str {
18        self.function.location().as_str(source).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, source: &[u8]) -> bool {
31        self.function_name(source).contains(pattern)
32    }
33
34    /// Check if function name ends with a pattern
35    pub fn function_ends_with(&self, pattern: &str, source: &[u8]) -> bool {
36        self.function_name(source).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    /// Automatically unwraps Argument::Expression
50    pub fn get_arg(&self, index: usize) -> Option<&Expression> {
51        match self.arguments.get(index)? {
52            Argument::Expression(expr) => Some(expr.as_ref()),
53        }
54    }
55
56    /// Get argument as source text
57    pub fn get_arg_text<'a>(&self, index: usize, source: &'a [u8]) -> Option<&'a str> {
58        match self.arguments.get(index)? {
59            Argument::Expression(expr) => expr.to_source_string(source),
60        }
61    }
62
63    /// Check if the argument at the given index exists and matches the
64    /// predicate
65    pub fn has_arg_matching<F>(&self, index: usize, predicate: F) -> bool
66    where
67        F: FnOnce(&Expression) -> bool,
68    {
69        self.get_arg(index).is_some_and(predicate)
70    }
71
72    /// Check if the argument at the given index contains a reference to the
73    /// specified variable Handles both plain identifiers and field access
74    /// (e.g., obj->field)
75    pub fn arg_contains_variable(&self, index: usize, var_name: &str, source: &[u8]) -> bool {
76        self.has_arg_matching(index, |expr| {
77            expr.extract_variable_name(source)
78                .is_some_and(|name| name == var_name)
79        })
80    }
81
82    /// Check if this looks like a macro call (ALL_CAPS or ends with _)
83    /// Examples: I_, N_, G_STRINGIFY, GINT_TO_POINTER
84    pub fn is_likely_macro(&self, source: &[u8]) -> bool {
85        let name = self.function_name(source);
86        name.chars().all(|c| c.is_uppercase() || c == '_') || name.ends_with('_')
87    }
88
89    /// Extract string literal from argument, unwrapping macro calls like
90    /// I_("string") This is useful for g_param_spec calls where the name
91    /// might be I_("property-name")
92    pub fn extract_string_from_arg(&self, index: usize) -> Option<String> {
93        let Argument::Expression(expr) = self.arguments.get(index)?;
94        expr.extract_string_value()
95    }
96
97    /// Check if this call is a GObject allocation function
98    /// Recognizes g_object_new, g_new, and various other allocation patterns
99    pub fn is_allocation_call(&self) -> bool {
100        if let Some(name) = self.function_name_str() {
101            matches!(
102                name,
103                "g_object_new"
104                    | "g_object_new_with_properties"
105                    | "g_type_create_instance"
106                    | "g_new"
107                    | "g_new0"
108                    | "g_try_new"
109                    | "g_try_new0"
110                    | "g_malloc"
111                    | "g_malloc0"
112                    | "g_strdup"
113                    | "g_strndup"
114                    | "g_file_new_for_path"
115                    | "g_file_new_for_uri"
116                    | "g_file_new_tmp"
117                    | "g_variant_new"
118                    | "g_variant_ref_sink"
119                    | "g_bytes_new"
120                    | "g_bytes_new_take"
121                    | "g_hash_table_new"
122                    | "g_hash_table_new_full"
123                    | "g_array_new"
124                    | "g_ptr_array_new"
125                    | "g_error_new"
126                    | "g_error_new_literal"
127            ) || name.ends_with("_new")
128                || name.ends_with("_get_instance")
129                || name.contains("_new_")
130                || name.contains("_create")
131        } else {
132            false
133        }
134    }
135
136    /// Check if this call is a GObject cleanup/free function
137    /// Recognizes g_object_unref, g_free, and various other cleanup patterns
138    pub fn is_cleanup_call(&self) -> bool {
139        if let Some(name) = self.function_name_str() {
140            matches!(
141                name,
142                "g_object_unref"
143                    | "g_clear_object"
144                    | "g_clear_pointer"
145                    | "g_error_free"
146                    | "g_clear_error"
147                    | "g_free"
148                    | "g_clear_handle_id"
149                    | "g_clear_signal_handler"
150                    | "g_list_free"
151                    | "g_list_free_full"
152                    | "g_slist_free"
153                    | "g_slist_free_full"
154                    | "g_hash_table_unref"
155                    | "g_hash_table_destroy"
156                    | "g_bytes_unref"
157                    | "g_variant_unref"
158                    | "g_array_unref"
159                    | "g_array_free"
160                    | "g_ptr_array_unref"
161                    | "g_ptr_array_free"
162            ) || name.ends_with("_unref")
163                || name.ends_with("_free")
164                || name.ends_with("_destroy")
165        } else {
166            false
167        }
168    }
169}
170
171#[derive(Debug, Clone, Serialize)]
172#[serde(untagged)]
173pub enum Argument {
174    Expression(Box<Expression>),
175}
176
177impl Argument {
178    /// Convert this argument back to source text
179    pub fn to_source_string<'a>(&self, source: &'a [u8]) -> Option<&'a str> {
180        match self {
181            Self::Expression(expr) => expr.to_source_string(source),
182        }
183    }
184
185    /// Check if this argument is a string literal or macro wrapping a string
186    pub fn is_string_or_macro_string(&self) -> bool {
187        let Self::Expression(expr) = self;
188        expr.is_string_or_macro_string()
189    }
190
191    /// Check if this argument is NULL
192    pub fn is_null(&self) -> bool {
193        let Self::Expression(expr) = self;
194        expr.is_null()
195    }
196
197    /// Extract string value from this argument, unwrapping macros
198    pub fn extract_string_value(&self) -> Option<String> {
199        let Self::Expression(expr) = self;
200        expr.extract_string_value()
201    }
202}