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